diff --git a/.gitignore b/.gitignore index 8ee9c4d382..4d27e6b031 100644 --- a/.gitignore +++ b/.gitignore @@ -444,3 +444,6 @@ server.pid # Crowdin translation automation working directory /.crowdin-translate/ + +# Third-party source code reference files +ThirdpartyReference/ diff --git a/.skills/mcc-dev-workflow/SKILL.md b/.skills/mcc-dev-workflow/SKILL.md index 0661489209..54b24c9f36 100644 --- a/.skills/mcc-dev-workflow/SKILL.md +++ b/.skills/mcc-dev-workflow/SKILL.md @@ -13,7 +13,7 @@ Use this skill when the task needs a real local server loop, not just code readi - Runtime target: `.NET 10` / `net10.0` - Environment: Linux, macOS, or WSL with Java, tmux, python3, and dotnet available - Default server root after `source tools/mcc-env.sh`: `${MCC_SERVERS:-/MinecraftOfficial/downloads}` -- Default validation target when the user does not specify a version: `1.21.11` +- Default validation target when the user does not specify a server directory: `1.21.11-Vanilla` ## Console modes @@ -51,13 +51,13 @@ Two worktrees can debug against one shared server like this: # worktree A cd ~/Minecraft/Minecraft-Console-Client source tools/mcc-env.sh -mc-start 1.21.11 -mcc-debug -v 1.21.11 --file-input +mc-start 1.21.11-Vanilla +mcc-debug -v 1.21.11-Vanilla --file-input # worktree B cd ~/Minecraft/Minecraft-Console-Client-foo source tools/mcc-env.sh -mcc-debug -v 1.21.11 --file-input +mcc-debug -v 1.21.11-Vanilla --file-input # from each worktree, mcc-* targets that worktree's default session mcc-state @@ -84,8 +84,8 @@ Before scripted runs, especially on macOS or in a reused tmux environment: ```bash source tools/mcc-env.sh -mcc-preflight 1.21.11 -mc-reset-test-env 1.21.11 +mcc-preflight 1.21.11-Vanilla +mc-reset-test-env 1.21.11-Vanilla ``` `mcc-preflight` checks Java, tmux, dotnet, python3, and server directories. It also resolves common Homebrew Java paths on macOS. `mc-reset-test-env` clears stale tmux sessions and stale `stdin.pipe` files before they turn into misleading startup failures. @@ -107,16 +107,16 @@ Interactive shell: source tools/mcc-env.sh SESSION="$(_mcc_resolve_session)" USERNAME="$(_mcc_resolve_username "$SESSION")" -mc-start 1.21.11 -mc-log 1.21.11 100 +mc-start 1.21.11-Vanilla +mc-log 1.21.11-Vanilla 100 mc-rcon "op $USERNAME" -mc-stop 1.21.11 +mc-stop 1.21.11-Vanilla ``` Non-interactive shell: ```bash -tools/start-server.sh 1.21.11 +tools/start-server.sh 1.21.11-Vanilla tools/mc-rcon.sh "op mcc_smoke_a" ``` @@ -135,19 +135,19 @@ The `tools/mcc-debug.sh` script handles build, server startup, config preparatio source tools/mcc-env.sh # Classic mode with FileInput (script-driven debugging): -mcc-debug -v 1.21.11 --file-input +mcc-debug -v 1.21.11-Vanilla --file-input # Classic mode interactive (attach via tmux): -mcc-debug -v 1.21.11 +mcc-debug -v 1.21.11-Vanilla # TUI mode: -mcc-debug -v 1.21.11 -m tui +mcc-debug -v 1.21.11-Vanilla -m tui # With debug messages enabled from start: -mcc-debug -v 1.21.11 --file-input --debug-on +mcc-debug -v 1.21.11-Vanilla --file-input --debug-on # Skip build (already built): -mcc-debug -v 1.21.11 --file-input --no-build +mcc-debug -v 1.21.11-Vanilla --file-input --no-build ``` ### What mcc-debug.sh does @@ -202,7 +202,7 @@ For agents calling MCC commands programmatically: ```bash source tools/mcc-env.sh SESSION="smoke-a" -mcc-debug -v 1.21.11 --file-input --session "$SESSION" --no-build +mcc-debug -v 1.21.11-Vanilla --file-input --session "$SESSION" --no-build # Send commands: mcc-cmd --session "$SESSION" "debug state" @@ -215,7 +215,7 @@ mcc-log-mcc --session "$SESSION" # Stop: mcc-cmd --session "$SESSION" "quit" mcc-kill --session "$SESSION" -mc-stop 1.21.11 +mc-stop 1.21.11-Vanilla ``` ### Interactive workflow @@ -223,7 +223,7 @@ mc-stop 1.21.11 ```bash source tools/mcc-env.sh SESSION="live-a" -mcc-debug -v 1.21.11 --session "$SESSION" +mcc-debug -v 1.21.11-Vanilla --session "$SESSION" # In another terminal: tmux attach -t "mcc-$SESSION" @@ -245,7 +245,7 @@ TUI mode runs Consolonia full-screen in a tmux session. Key differences: ```bash source tools/mcc-env.sh SESSION="tui-a" -mcc-debug -v 1.21.11 -m tui --session "$SESSION" --no-build +mcc-debug -v 1.21.11-Vanilla -m tui --session "$SESSION" --no-build # Cannot use mcc-cmd (no FileInput); must use tmux send-keys: tmux send-keys -t "mcc-$SESSION" "/debug state" Enter @@ -330,12 +330,12 @@ If a scripted run fails before MCC joins, check for a harness problem before ass ## Typical debug loop 1. `source tools/mcc-env.sh` -2. `mcc-debug -v 1.21.11 --file-input` (or `-m tui`) +2. `mcc-debug -v 1.21.11-Vanilla --file-input` (or `-m tui`) 3. Confirm `Server was successfully joined` in log 4. `mcc-cmd "debug state"` to verify MCC state 5. Run test commands 6. Inspect log output -7. `mcc-cmd "quit"` and `mc-stop 1.21.11` +7. `mcc-cmd "quit"` and `mc-stop 1.21.11-Vanilla` 8. Edit code, rebuild, repeat ## Debugging tips diff --git a/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh b/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh index 1e30f6739c..bb09f2de1b 100644 --- a/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh +++ b/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh @@ -14,6 +14,20 @@ Usage: EOF } +normalize_output_ini() { + local raw_output_ini="$1" + local spill_dir + + if [[ "$raw_output_ini" == *.ini || "$raw_output_ini" == */* ]]; then + printf '%s\n' "$raw_output_ini" + return 0 + fi + + spill_dir="${MCC_CONFIG_SPILL_DIR:-$REPO_ROOT/.tmp/mcc-config-spill}" + mkdir -p "$spill_dir" + printf '%s/%s.ini\n' "$spill_dir" "$raw_output_ini" +} + if [[ $# -lt 2 || $# -gt 4 ]]; then usage exit 1 @@ -24,17 +38,27 @@ OUTPUT_INI="" MC_VERSION="" LOGIN_NAME="" -if [[ $# -ge 3 && -f "$1" ]]; then +if [[ $# -ge 3 && "$2" == *.ini ]]; then TEMPLATE_INI="$1" OUTPUT_INI="$2" MC_VERSION="$3" LOGIN_NAME="${4:-MCCBot}" +elif [[ $# -eq 4 && "$1" == *.ini && -f "$1" ]]; then + TEMPLATE_INI="$1" + OUTPUT_INI="$2" + MC_VERSION="$3" + LOGIN_NAME="$4" +elif [[ $# -eq 4 ]]; then + usage + exit 1 else OUTPUT_INI="$1" MC_VERSION="$2" LOGIN_NAME="${3:-MCCBot}" fi +OUTPUT_INI="$(normalize_output_ini "$OUTPUT_INI")" + ACCOUNT_TYPE="${MCC_TEST_ACCOUNT_TYPE:-mojang}" PASSWORD_VALUE="${MCC_TEST_PASSWORD-}" diff --git a/.skills/mcc-integration-testing/scripts/run_achievements_test.sh b/.skills/mcc-integration-testing/scripts/run_achievements_test.sh index 1484acd6ed..46f0c184ef 100755 --- a/.skills/mcc-integration-testing/scripts/run_achievements_test.sh +++ b/.skills/mcc-integration-testing/scripts/run_achievements_test.sh @@ -126,8 +126,6 @@ cleanup() { wait "$MCC_PID" 2>/dev/null || true fi - mc-stop "$SERVER_DIR" --confirm >/dev/null 2>&1 || true - wait_for_server_stop "$SERVER_DIR" 20 >/dev/null 2>&1 || true ln -sfn "$RUN_DIR" "$LATEST_LINK" write_summary } diff --git a/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh b/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh index 181862a8cd..73e61ef24c 100755 --- a/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh +++ b/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh @@ -34,9 +34,6 @@ cleanup() { mcc-cmd --session "$SESSION_NAME" "quit" >/dev/null 2>&1 || true sleep 2 mcc-kill --session "$SESSION_NAME" >/dev/null 2>&1 || true - - mc-stop "$VERSION" --confirm >/dev/null 2>&1 || true - wait_for_server_stop "$VERSION" 20 >/dev/null 2>&1 || true } trap cleanup EXIT diff --git a/.skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh b/.skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh index 9f422d2867..7a5f48a197 100755 --- a/.skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh +++ b/.skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh @@ -93,8 +93,6 @@ cleanup() { sleep 1 mcc-kill --session "$SESSION_A" >/dev/null 2>&1 || true mcc-kill --session "$SESSION_B" >/dev/null 2>&1 || true - mc-stop "$VERSION" --confirm >/dev/null 2>&1 || true - wait_for_server_stop "$VERSION" 20 >/dev/null 2>&1 || true } trap cleanup EXIT diff --git a/.skills/mcc-version-adaptation/SKILL.md b/.skills/mcc-version-adaptation/SKILL.md index 706399cbf8..c5cc59b4a5 100644 --- a/.skills/mcc-version-adaptation/SKILL.md +++ b/.skills/mcc-version-adaptation/SKILL.md @@ -12,11 +12,11 @@ Systematic workflow for updating Minecraft Console Client to support a new Minec - Decompiled server source for both the old and new MC versions in `$MCC_REPO/MinecraftOfficial/-decompiled/` - If missing, decompile and download server.jar: ```bash - $MCC_REPO/tools/decompile.sh --version + $MCC_REPO/tools/decompile.sh --version -Vanilla ``` - This auto-downloads `MinecraftDecompiler.jar` if needed, produces the decompiled source, and downloads `server.jar` into `$MCC_SERVERS//`. -- `tools/decompile.sh` depends on official mappings. For older versions where it refuses to decompile, fall back to a raw Java decompiler such as `cfr-decompiler` against `$MCC_SERVERS//server.jar`. That fallback is good enough for packet inspection and registration order checks even when the output is obfuscated. -- A test server of the target version in `$MCC_SERVERS//` (see `mcc-dev-workflow` skill) + This auto-downloads `MinecraftDecompiler.jar` if needed, produces the decompiled source under `$MCC_REPO/MinecraftOfficial/-decompiled/`, and downloads `server.jar` into `$MCC_SERVERS/-Vanilla/`. +- `tools/decompile.sh` depends on official mappings. For older versions where it refuses to decompile, fall back to a raw Java decompiler such as `cfr-decompiler` against `$MCC_SERVERS/-Vanilla/server.jar`. That fallback is good enough for packet inspection and registration order checks even when the output is obfuscated. +- A test server of the target version in `$MCC_SERVERS/-Vanilla/` (see `mcc-dev-workflow` skill) ## Step 0: Generate Server Reports (CRITICAL since 1.21.9) diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..77fe4fa1bf --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/MinecraftClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/MinecraftClient.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/MinecraftClient.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index b82cc5bf67..9294cacb8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ - Init submodules first: `git submodule update --init --recursive` - Build for local development: `source tools/mcc-env.sh && mcc-build` - Publish (matches CI shape): `source tools/mcc-env.sh && mcc-publish --rid ` -- Run/debug from source: `source tools/mcc-env.sh && mcc-debug -v 1.21.11 --file-input` +- Run/debug from source: `source tools/mcc-env.sh && mcc-debug -v 1.21.11-Vanilla --file-input` - Docs: `cd docs && npm install && npm run docs:dev` or `npm run docs:build` - Docker: `cd Docker && docker build -t minecraft-console-client:latest .` - Tests: no dedicated test project is present in the main solution. diff --git a/MinecraftClient.Tests/MinecraftClient.Tests.csproj b/MinecraftClient.Tests/MinecraftClient.Tests.csproj new file mode 100644 index 0000000000..937d0e5690 --- /dev/null +++ b/MinecraftClient.Tests/MinecraftClient.Tests.csproj @@ -0,0 +1,25 @@ + + + net10.0 + enable + enable + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs b/MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs new file mode 100644 index 0000000000..9285779b8b --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs @@ -0,0 +1,99 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class ClimbFallTemplateTests +{ + [Fact] + public void ClimbTemplate_AscendsLadderColumn_CompletesOverTarget() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 2); + BuildLadder(world, x: 0, z: 0, bottomY: 80, topY: 84); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(0.5, 84, 0.5), + MoveType = MoveType.Climb, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new ClimbTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f); + physics.OnClimbable = true; + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 220, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + AssertNearTargetBlock(finalPos, segment.End); + } + + [Fact] + public void ClimbTemplate_DescendsLadderColumn_CompletesOverTarget() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 2); + BuildLadder(world, x: 0, z: 0, bottomY: 80, topY: 84); + + var segment = new PathSegment + { + Start = new Location(0.5, 84, 0.5), + End = new Location(0.5, 80, 0.5), + MoveType = MoveType.Climb, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new ClimbTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 180f); + physics.OnClimbable = true; + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 220, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + AssertNearTargetBlock(finalPos, segment.End); + } + + [Fact] + public void FallTemplate_DropsStraightDown_CompletesOnFloor() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 4, max: 8); + + var segment = new PathSegment + { + Start = new Location(5.5, 85, 5.5), + End = new Location(5.5, 80, 5.5), + MoveType = MoveType.Fall, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new FallTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 90f); + physics.OnGround = false; + physics.DeltaMovement = new Vec3d(0, -0.15, 0); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 260, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + AssertNearTargetBlock(finalPos, segment.End); + } + + private static void AssertNearTargetBlock(Location actual, Location target) + { + Assert.True(Math.Abs(actual.Y - target.Y) < 0.6, $"Expected final Y near {target.Y:F2}, got {actual.Y:F2}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(actual, target), + $"Expected the final footprint to stay within {target}, got {actual}"); + } + + private static void BuildLadder(World world, int x, int z, int bottomY, int topY) + { + for (int y = bottomY; y <= topY; y++) + { + FlatWorldTestBuilder.SetClimbable(world, x, y, z); + } + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs new file mode 100644 index 0000000000..b53d5e9a43 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs @@ -0,0 +1,225 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Tests.Pathing.Execution.Contracts; + +public sealed class PathingContractStore +{ + private readonly IReadOnlyDictionary planners; + private readonly IReadOnlyDictionary timings; + + private PathingContractStore( + IReadOnlyDictionary planners, + IReadOnlyDictionary timings) + { + this.planners = planners; + this.timings = timings; + } + + public static PathingContractStore LoadFromRepositoryRoot() + { + string rootPath = FindRepositoryRoot(); + string pathingDir = Path.Combine(rootPath, "MinecraftClient.Tests", "TestData", "Pathing"); + + string plannerJson = File.ReadAllText(Path.Combine(pathingDir, "pathing-planner-contracts.json")); + string timingJson = File.ReadAllText(Path.Combine(pathingDir, "pathing-timing-budgets.json")); + + return LoadFromJson(plannerJson, timingJson); + } + + public static PathingContractStore LoadFromJson(string plannerJson, string timingJson) + { + ArgumentException.ThrowIfNullOrWhiteSpace(plannerJson); + ArgumentException.ThrowIfNullOrWhiteSpace(timingJson); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + options.Converters.Add(new JsonStringEnumConverter()); + + PathingPlannerContract[] plannerContracts = JsonSerializer.Deserialize(plannerJson, options) + ?? throw new InvalidOperationException("Failed to deserialize planner contracts."); + PathingTimingBudget[] timingBudgets = JsonSerializer.Deserialize(timingJson, options) + ?? throw new InvalidOperationException("Failed to deserialize timing budgets."); + + Dictionary plannerByScenario = BuildPlannerDictionary(plannerContracts); + Dictionary timingByScenario = BuildTimingDictionary(timingBudgets); + ValidateScenarioSetConsistency(plannerByScenario, timingByScenario); + ValidateScenarioSegmentAlignment(plannerByScenario, timingByScenario); + + return new PathingContractStore( + plannerByScenario, + timingByScenario); + } + + public PathingPlannerContract GetPlanner(string id) + { + ArgumentException.ThrowIfNullOrWhiteSpace(id); + return planners.TryGetValue(id, out PathingPlannerContract? contract) + ? contract + : throw new KeyNotFoundException($"Planner contract '{id}' was not found."); + } + + public PathingTimingBudget GetTiming(string id) + { + ArgumentException.ThrowIfNullOrWhiteSpace(id); + return timings.TryGetValue(id, out PathingTimingBudget? budget) + ? budget + : throw new KeyNotFoundException($"Timing budget '{id}' was not found."); + } + + private static string FindRepositoryRoot() + { + DirectoryInfo? current = new(AppContext.BaseDirectory); + + while (current is not null) + { + if (File.Exists(Path.Combine(current.FullName, "MinecraftClient.sln"))) + return current.FullName; + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Unable to locate repository root from current test execution directory."); + } + + private static Dictionary BuildPlannerDictionary(IEnumerable contracts) + { + var result = new Dictionary(StringComparer.Ordinal); + foreach (PathingPlannerContract contract in contracts) + { + PathingPlannerContract normalized = ValidateAndNormalizePlanner(contract); + if (!result.TryAdd(normalized.ScenarioId, normalized)) + throw new InvalidDataException($"Duplicate planner contract scenario id '{normalized.ScenarioId}'."); + } + + return result; + } + + private static Dictionary BuildTimingDictionary(IEnumerable budgets) + { + var result = new Dictionary(StringComparer.Ordinal); + foreach (PathingTimingBudget budget in budgets) + { + PathingTimingBudget normalized = ValidateAndNormalizeTiming(budget); + if (!result.TryAdd(normalized.ScenarioId, normalized)) + throw new InvalidDataException($"Duplicate timing budget scenario id '{normalized.ScenarioId}'."); + } + + return result; + } + + private static PathingPlannerContract ValidateAndNormalizePlanner(PathingPlannerContract contract) + { + if (string.IsNullOrWhiteSpace(contract.ScenarioId)) + throw new InvalidDataException("Planner contract has blank scenario id."); + + if (contract.Segments is null) + throw new InvalidDataException($"Planner contract '{contract.ScenarioId}' has null segments."); + + if (contract.ExpectedStatus != PathStatus.Failed && contract.Segments.Count == 0) + throw new InvalidDataException($"Planner contract '{contract.ScenarioId}' must contain at least one segment unless the expected status is Failed."); + + var normalizedSegments = new List(contract.Segments.Count); + for (int i = 0; i < contract.Segments.Count; i++) + { + PathingPlannerSegmentContract segment = contract.Segments[i]; + normalizedSegments.Add(segment); + } + + return contract with { Segments = normalizedSegments.AsReadOnly() }; + } + + private static PathingTimingBudget ValidateAndNormalizeTiming(PathingTimingBudget budget) + { + if (string.IsNullOrWhiteSpace(budget.ScenarioId)) + throw new InvalidDataException("Timing budget has blank scenario id."); + + if (budget.Segments is null) + throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' has null segments."); + + if (budget.ExpectedTotalTicks < 0 || budget.MaxTotalTicks < 0) + throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' total ticks must be nonnegative."); + + if (budget.ExpectedTotalTicks > budget.MaxTotalTicks) + throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' has ExpectedTotalTicks greater than MaxTotalTicks."); + + if (budget.Segments.Count == 0 && (budget.ExpectedTotalTicks != 0 || budget.MaxTotalTicks != 0)) + throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' must use zero totals when it has no segments."); + + var normalizedSegments = new List(budget.Segments.Count); + int expectedSegmentTicksSum = 0; + int maxSegmentTicksSum = 0; + for (int i = 0; i < budget.Segments.Count; i++) + { + PathingSegmentTimingBudget segment = budget.Segments[i]; + if (segment.ExpectedTicks < 0 || segment.MaxTicks < 0) + throw new InvalidDataException($"Timing budget '{budget.ScenarioId}' segment {i} has negative tick values."); + + 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() }; + } + + private static void ValidateScenarioSetConsistency( + IReadOnlyDictionary plannersByScenario, + IReadOnlyDictionary timingsByScenario) + { + string[] plannerOnly = plannersByScenario.Keys.Except(timingsByScenario.Keys, StringComparer.Ordinal).OrderBy(id => id, StringComparer.Ordinal).ToArray(); + string[] timingOnly = timingsByScenario.Keys.Except(plannersByScenario.Keys, StringComparer.Ordinal).OrderBy(id => id, StringComparer.Ordinal).ToArray(); + + if (plannerOnly.Length == 0 && timingOnly.Length == 0) + return; + + string plannerOnlyList = plannerOnly.Length == 0 ? "" : string.Join(", ", plannerOnly); + string timingOnlyList = timingOnly.Length == 0 ? "" : string.Join(", ", timingOnly); + throw new InvalidDataException( + $"Planner/timing scenario set mismatch. Missing timing entries for: {plannerOnlyList}. Missing planner entries for: {timingOnlyList}."); + } + + private static void ValidateScenarioSegmentAlignment( + IReadOnlyDictionary plannersByScenario, + IReadOnlyDictionary timingsByScenario) + { + foreach ((string scenarioId, PathingPlannerContract planner) in plannersByScenario) + { + PathingTimingBudget timing = timingsByScenario[scenarioId]; + if (planner.Segments.Count != timing.Segments.Count) + { + throw new InvalidDataException( + $"Scenario '{scenarioId}' segment count mismatch. Planner has {planner.Segments.Count} segments, timing has {timing.Segments.Count}."); + } + + for (int i = 0; i < planner.Segments.Count; i++) + { + MoveType plannerMove = planner.Segments[i].MoveType; + MoveType timingMove = timing.Segments[i].MoveType; + if (plannerMove != timingMove) + { + throw new InvalidDataException( + $"Scenario '{scenarioId}' segment {i} move mismatch. Planner has {plannerMove}, timing has {timingMove}."); + } + } + } + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs new file mode 100644 index 0000000000..7e004da72e --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs @@ -0,0 +1,15 @@ +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Tests.Pathing.Execution.Contracts; + +public readonly record struct PathingBlock(int X, int Y, int Z); + +public sealed record PathingPlannerSegmentContract( + MoveType MoveType, + PathingBlock StartBlock, + PathingBlock EndBlock); + +public sealed record PathingPlannerContract( + string ScenarioId, + PathStatus ExpectedStatus, + IReadOnlyList Segments); diff --git a/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingTimingBudget.cs b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingTimingBudget.cs new file mode 100644 index 0000000000..b7126274de --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Contracts/PathingTimingBudget.cs @@ -0,0 +1,14 @@ +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Tests.Pathing.Execution.Contracts; + +public sealed record PathingSegmentTimingBudget( + MoveType MoveType, + int ExpectedTicks, + int MaxTicks); + +public sealed record PathingTimingBudget( + string ScenarioId, + int ExpectedTotalTicks, + int MaxTotalTicks, + IReadOnlyList Segments); diff --git a/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs b/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs new file mode 100644 index 0000000000..41a2cf8ed3 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using MinecraftClient.Mapping; +using MinecraftClient.Mapping.BlockPalettes; +using MinecraftClient.Physics; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class FlatWorldTestBuilder +{ + private static readonly Lock InitLock = new(); + private static bool _defaultsLoaded; + private static readonly Dictionary MaterialIds = new(); + + public static World CreateStoneFloor(int floorY = 79, int min = -32, int max = 32) + { + EnsureDefaultDimensionsLoaded(); + World.SetDimension("minecraft:overworld"); + + var world = new World(); + int minChunk = (int)Math.Floor(min / 16.0); + int maxChunk = (int)Math.Floor(max / 16.0); + + for (int chunkX = minChunk; chunkX <= maxChunk; chunkX++) + { + for (int chunkZ = minChunk; chunkZ <= maxChunk; chunkZ++) + { + world[chunkX, chunkZ] = new ChunkColumn(24) { FullyLoaded = true }; + } + } + + for (int x = min; x <= max; x++) + { + for (int z = min; z <= max; z++) + { + SetSolid(world, x, floorY, z); + } + } + + return world; + } + + public static void SetSolid(World world, int x, int y, int z) + { + SetMaterial(world, x, y, z, Material.Stone); + } + + public static void FillSolid(World world, int x1, int y1, int z1, int x2, int y2, int z2) + { + for (int x = Math.Min(x1, x2); x <= Math.Max(x1, x2); x++) + { + for (int y = Math.Min(y1, y2); y <= Math.Max(y1, y2); y++) + { + for (int z = Math.Min(z1, z2); z <= Math.Max(z1, z2); z++) + { + SetSolid(world, x, y, z); + } + } + } + } + + public static void ClearBox(World world, int x1, int y1, int z1, int x2, int y2, int z2) + { + for (int x = Math.Min(x1, x2); x <= Math.Max(x1, x2); x++) + { + for (int y = Math.Min(y1, y2); y <= Math.Max(y1, y2); y++) + { + for (int z = Math.Min(z1, z2); z <= Math.Max(z1, z2); z++) + { + world.SetBlock(new Location(x, y, z), Block.Air); + } + } + } + } + + public static void SetMaterial(World world, int x, int y, int z, Material material) + { + EnsureChunkColumn(world, x, z); + world.SetBlock(new Location(x, y, z), new Block(ResolveMaterialId(material))); + } + + public static void SetClimbable(World world, int x, int y, int z) + { + SetMaterial(world, x, y, z, Material.Ladder); + } + + private static void EnsureDefaultDimensionsLoaded() + { + lock (InitLock) + { + if (_defaultsLoaded) + return; + + Block.Palette = new Palette1219(); + World.LoadDefaultDimensions1206Plus(); + BlockShapes.Initialize(); + _defaultsLoaded = true; + } + } + + private static void EnsureChunkColumn(World world, int x, int z) + { + int chunkX = (int)Math.Floor(x / 16.0); + int chunkZ = (int)Math.Floor(z / 16.0); + if (world[chunkX, chunkZ] is null) + world[chunkX, chunkZ] = new ChunkColumn(24) { FullyLoaded = true }; + } + + private static ushort ResolveMaterialId(Material material) + { + lock (InitLock) + { + if (MaterialIds.TryGetValue(material, out ushort id)) + return id; + + for (int candidate = 0; candidate <= ushort.MaxValue; candidate++) + { + if (Block.Palette.FromId(candidate) == material) + { + ushort resolved = (ushort)candidate; + MaterialIds[material] = resolved; + return resolved; + } + } + + throw new InvalidOperationException($"Could not resolve a block id for material {material}"); + } + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilderTests.cs b/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilderTests.cs new file mode 100644 index 0000000000..bb85491644 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilderTests.cs @@ -0,0 +1,17 @@ +using MinecraftClient.Mapping; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class FlatWorldTestBuilderTests +{ + [Fact] + public void SetSolid_CreatesMissingChunkColumns() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 118, max: 126); + + FlatWorldTestBuilder.SetSolid(world, 123, 79, 110); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(123, 79, 110)).Type); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs b/MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs new file mode 100644 index 0000000000..f8f4b69cdb --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs @@ -0,0 +1,1006 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class GroundedTemplateConvergenceTests +{ + [Fact] + public void PlayerPhysics_SprintingGroundTravel_IsFasterThanWalking() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 16); + var walking = TemplateSimulationRunner.CreateGroundedPhysics(new Location(0.5, 80, 0.5), yaw: 270f); + var sprinting = TemplateSimulationRunner.CreateGroundedPhysics(new Location(0.5, 80, 0.5), yaw: 270f); + + for (int tick = 0; tick < 8; tick++) + { + walking.ApplyInput(new MovementInput { Forward = true }); + walking.Tick(world); + + sprinting.ApplyInput(new MovementInput { Forward = true, Sprint = true }); + sprinting.Tick(world); + } + + double walkingTravel = walking.Position.X - 0.5; + double sprintingTravel = sprinting.Position.X - 0.5; + + Assert.True( + sprintingTravel > walkingTravel + 0.05, + $"walkingTravel={walkingTravel:F4} sprintingTravel={sprintingTravel:F4} walkVel={walking.DeltaMovement} sprintVel={sprinting.DeltaMovement}"); + } + + [Fact] + public void WalkTemplate_FinalStop_Completes_WhenFootprintStaysInsideTargetBlock() + { + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 160, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void WalkTemplate_FinalStop_Completes_WhenCenterStopsInsideTargetBlockNearEdge() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var segment = new PathSegment + { + Start = new Location(2.5, 80, 0.5), + End = new Location(3.5, 80, 0.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new WalkTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(3.2897, 80.0, 0.5), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + var input = new MovementInput(); + TemplateState state = template.Tick(new Location(physics.Position.X, physics.Position.Y, physics.Position.Z), physics, input, world); + + Assert.Equal(TemplateState.Complete, state); + } + + [Fact] + public void WalkTemplate_FinalStop_Completes_FromLiveNearGoalState_WithoutFailure() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + var segment = new PathSegment + { + Start = new Location(102.5, 80, 100.5), + End = new Location(103.5, 80, 100.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new WalkTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(103.36, 80.0, 100.50), + DeltaMovement = new Vec3d(0.0346, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 40, out _); + + Assert.Equal(TemplateState.Complete, state); + } + + [Fact] + public void WalkTemplate_PrepareJump_CompletesWithoutSettlingOnRunUpBlock() + { + 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, + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 60, out _); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(physics.DeltaMovement.X > 0.02); + } + + [Fact] + public void WalkTemplate_PrepareJump_WithPlannerHints_CompletesOnRunUpBlock() + { + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 120, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, current.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void AscendTemplate_DiagonalPrepareJump_WithPlannerHints_CompletesOnRunUpBlock() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -1, max: 6); + FlatWorldTestBuilder.ClearBox(world, -1, 79, -1, 6, 84, 6); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 1, 80, 1); + FlatWorldTestBuilder.SetSolid(world, 3, 80, 3); + + var current = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 81, 1.5), + MoveType = MoveType.Ascend, + ExitTransition = PathTransitionType.PrepareJump, + ExitHints = new PathTransitionHints(1, 1, 0.10, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(1.5, 81, 1.5), + End = new Location(3.5, 81, 3.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new AscendTemplate(current, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 315f); + var input = new MovementInput(); + var trace = new List(); + TemplateState state = TemplateState.InProgress; + Location finalPos = current.Start; + for (int tick = 0; tick < 140; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (tick < 20 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={tick} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},J={input.Jump},S={input.Sprint})"); + } + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + state == TemplateState.Complete, + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, current.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void AscendTemplate_AfterTraversePrepareJump_CompletesWithinTwentyTicks() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -1, max: 6); + FlatWorldTestBuilder.ClearBox(world, -1, 79, -1, 6, 84, 2); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 1, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 80, 0); + FlatWorldTestBuilder.SetSolid(world, 3, 81, 0); + + var traverse = 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.0, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + var ascend = new PathSegment + { + Start = new Location(1.5, 80, 0.5), + End = new Location(2.5, 81, 0.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(2.5, 81, 0.5), + End = new Location(3.5, 82, 0.5), + MoveType = MoveType.Ascend, + ExitTransition = PathTransitionType.FinalStop + }; + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(traverse.Start, yaw: 270f); + + TemplateState traverseState = TemplateSimulationRunner.Run(new WalkTemplate(traverse, ascend), physics, world, maxTicks: 80, out _); + + var template = new AscendTemplate(ascend, next); + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + int elapsedTicks = 0; + Location finalPos = ascend.Start; + for (; elapsedTicks < 80; elapsedTicks++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + } + + Assert.Equal(TemplateState.Complete, traverseState); + Assert.Equal(TemplateState.Complete, state); + Assert.True(elapsedTicks <= 20, $"elapsedTicks={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void AscendTemplate_PrepareJump_CompletesFromOppositeYawWithinTwentyTicks() + { + 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 = TemplateState.InProgress; + int elapsedTicks = 0; + Location finalPos = segment.Start; + var trace = new List(); + for (; elapsedTicks < 80; elapsedTicks++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (elapsedTicks < 20 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={elapsedTicks} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},J={input.Jump},S={input.Sprint})"); + } + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.Equal(TemplateState.Complete, state); + Assert.True(elapsedTicks <= 20, $"elapsedTicks={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, segment.End), + $"elapsedTicks={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void AscendTemplate_PrepareJump_CompletesFromOffCenterRunUpState() + { + 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 = new PlayerPhysics + { + Position = new Vec3d(340.25, 80.0, 340.25), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + var trace = new List(); + for (int tick = 0; tick < 80; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (tick < 20 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={tick} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},J={input.Jump},S={input.Sprint})"); + } + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + state == TemplateState.Complete, + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, segment.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void WalkTemplate_DiagonalPrepareJumpIntoAscend_CompletesFromTargetBlockEntry() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 418, max: 426); + FlatWorldTestBuilder.ClearBox(world, 418, 79, 418, 426, 84, 424); + FlatWorldTestBuilder.SetSolid(world, 420, 79, 420); + FlatWorldTestBuilder.SetSolid(world, 421, 79, 421); + FlatWorldTestBuilder.SetSolid(world, 422, 79, 422); + FlatWorldTestBuilder.SetSolid(world, 423, 80, 422); + FlatWorldTestBuilder.SetSolid(world, 424, 81, 422); + + var current = new PathSegment + { + Start = new Location(421.5, 80, 421.5), + End = new Location(422.5, 80, 422.5), + MoveType = MoveType.Diagonal, + ExitTransition = PathTransitionType.PrepareJump, + ExitHints = new PathTransitionHints(1, 0, 0.0, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(422.5, 80, 422.5), + End = new Location(423.5, 81, 422.5), + MoveType = MoveType.Ascend, + ExitTransition = PathTransitionType.PrepareJump, + ExitHints = new PathTransitionHints(1, 0, 0.0, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + + var template = new WalkTemplate(current, next); + var physics = new PlayerPhysics + { + Position = new Vec3d(422.25, 80.0, 422.25), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 315f, + Pitch = 0f + }; + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + var trace = new List(); + for (int tick = 0; tick < 80; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (tick < 20 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={tick} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},B={input.Back},S={input.Sprint})"); + } + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + state == TemplateState.Complete, + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, current.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void WalkTemplate_TurnIntoParkour_CompletesOnlyWhenTurnEntryIsSlowAndJumpReady() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 108, max: 128); + + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(121.5, 80, 110.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.08, 0.16, false, true, true, true, 12) + }; + var next = new PathSegment + { + Start = new Location(121.5, 80, 110.5), + End = new Location(121.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 template = new WalkTemplate(current, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + + Assert.Equal(TemplateState.Complete, state); + Assert.True( + TemplateFootingHelper.IsCenterInsideSupportStrip(finalPos, current.End, next.End), + $"finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.InRange(horizontalSpeed, 0.08, 0.20); + } + + [Fact] + public void DescendTemplate_LandingRecovery_CompletesOnLandingBlock() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + FlatWorldTestBuilder.ClearBox(world, 1, 79, 0, 1, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 1, 78, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 79, 0.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery + }; + + var template = new DescendTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 240, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void DescendTemplate_LandingRecovery_ChainCarry_CompletesBeforeSettling() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 360, max: 369); + FlatWorldTestBuilder.ClearBox(world, 360, 79, 358, 369, 85, 362); + FlatWorldTestBuilder.FillSolid(world, 362, 79, 360, 363, 79, 360); + FlatWorldTestBuilder.SetSolid(world, 364, 77, 360); + FlatWorldTestBuilder.SetSolid(world, 365, 75, 360); + FlatWorldTestBuilder.SetSolid(world, 366, 73, 360); + + var segment = new PathSegment + { + Start = new Location(363.5, 80, 360.5), + End = new Location(364.5, 78, 360.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.03, double.PositiveInfinity, false, true, false, true, 12) + }; + var next = new PathSegment + { + Start = new Location(364.5, 78, 360.5), + End = new Location(365.5, 76, 360.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.03, double.PositiveInfinity, false, true, false, true, 12) + }; + + var template = new DescendTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + var input = new MovementInput(); + var trace = new List(); + TemplateState state = TemplateState.InProgress; + Location finalPos = segment.Start; + + for (int tick = 0; tick < 160; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + trace.Add( + $"tick={tick} state={state} pos={pos} vel={physics.DeltaMovement} onGround={physics.OnGround} " + + $"input(F={input.Forward},B={input.Back},S={input.Sprint})"); + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End), $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True(physics.DeltaMovement.X >= 0.03, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void DescendTemplate_LandingRecovery_ShortChain_DoesNotSwingYawAfterPassingEndPlane() + { + static bool HasReachedSegmentEndPlane(Location pos, PathSegment segment, double tolerance = 0.05) + { + double dirX = Math.Sign(segment.End.X - segment.Start.X); + double dirZ = Math.Sign(segment.End.Z - segment.Start.Z); + double relX = pos.X - segment.End.X; + double relZ = pos.Z - segment.End.Z; + return relX * dirX + relZ * dirZ >= -tolerance; + } + + static double HeadingPenaltyDegrees(float yaw, PathSegment segment) + { + float targetYaw = (float)(-Math.Atan2(segment.HeadingX, segment.HeadingZ) / Math.PI * 180.0); + if (targetYaw < 0f) + targetYaw += 360f; + + float delta = targetYaw - yaw; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: -2, max: 10); + FlatWorldTestBuilder.ClearBox(world, -2, 1, -2, 10, 90, 2); + FlatWorldTestBuilder.FillSolid(world, 0, 79, 0, 3, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 4, 77, 0); + FlatWorldTestBuilder.SetSolid(world, 5, 75, 0); + FlatWorldTestBuilder.SetSolid(world, 6, 73, 0); + + var segment = new PathSegment + { + Start = new Location(4.5, 78, 0.5), + End = new Location(5.5, 76, 0.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.03, double.PositiveInfinity, false, true, false, true, 12) + }; + var next = new PathSegment + { + Start = new Location(5.5, 76, 0.5), + End = new Location(6.5, 74, 0.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.03, double.PositiveInfinity, false, true, false, true, 12) + }; + + var template = new DescendTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + var input = new MovementInput(); + var trace = new List(); + double maxHeadingPenaltyPastEnd = 0.0; + TemplateState state = TemplateState.InProgress; + Location finalPos = segment.Start; + + for (int tick = 0; tick < 120; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + bool pastEnd = HasReachedSegmentEndPlane(pos, segment); + if (!physics.OnGround && pastEnd) + maxHeadingPenaltyPastEnd = Math.Max(maxHeadingPenaltyPastEnd, HeadingPenaltyDegrees(physics.Yaw, segment)); + + trace.Add( + $"tick={tick} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} pastEnd={pastEnd} input(F={input.Forward},B={input.Back},S={input.Sprint})"); + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + maxHeadingPenaltyPastEnd <= 35.0, + $"maxHeadingPenaltyPastEnd={maxHeadingPenaltyPastEnd:F1} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void DescendTemplate_FinalStop_WithWallAndMisalignedYaw_CompletesOnLandingBlock() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 198, max: 204); + FlatWorldTestBuilder.ClearBox(world, 198, 79, 198, 204, 84, 202); + FlatWorldTestBuilder.FillSolid(world, 201, 79, 199, 203, 79, 201); + FlatWorldTestBuilder.SetSolid(world, 200, 80, 200); + FlatWorldTestBuilder.SetSolid(world, 200, 80, 199); + FlatWorldTestBuilder.SetSolid(world, 201, 80, 199); + FlatWorldTestBuilder.SetSolid(world, 202, 80, 199); + FlatWorldTestBuilder.SetSolid(world, 201, 81, 199); + FlatWorldTestBuilder.SetSolid(world, 202, 81, 199); + + var segment = new PathSegment + { + Start = new Location(200.5, 81, 200.5), + End = new Location(201.5, 80, 200.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new DescendTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f); + + var input = new MovementInput(); + var trace = new List(); + TemplateState state = TemplateState.InProgress; + Location finalPos = segment.Start; + for (int tick = 0; tick < 240; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (tick < 20 || state != TemplateState.InProgress || !physics.OnGround) + { + trace.Add($"tick={tick} state={state} pos={pos} vel={physics.DeltaMovement} onGround={physics.OnGround} input(F={input.Forward},B={input.Back},S={input.Sprint})"); + } + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void DescendTemplate_AppliesAirBrake_WhenPlannerRequiresBrake() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 4); + FlatWorldTestBuilder.ClearBox(world, -2, 79, -2, 4, 84, 2); + FlatWorldTestBuilder.SetSolid(world, 1, 79, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 81, 0.5), + End = new Location(1.5, 80, 0.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.0, 0.0, true, true, false, true, 12) + }; + + var template = new DescendTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.38, 80.56, 0.5), + DeltaMovement = new Vec3d(0.42, -0.22, 0.0), + OnGround = false, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(segment, null, pos, physics, world); + var input = new MovementInput(); + template.Tick(pos, physics, input, world); + + Assert.Equal(TransitionBrakingDecision.Brake, decision); + Assert.True(input.Back, $"decision={decision} input(F={input.Forward},B={input.Back},S={input.Sprint})"); + Assert.False(input.Forward, $"decision={decision} input(F={input.Forward},B={input.Back},S={input.Sprint})"); + } + + /// + /// Bug 2.1 regression: an island diagonal Ascend (heading (-X,+Z,+Y)) is + /// reached after a preceding cardinal Traverse has built up pure +Z ground + /// momentum. Without the execution-layer brake the perpendicular momentum + /// survives takeoff, collides with the +Z shoulder wall of the target + /// block, and the bot lands outside the target footprint. The template + /// must release Forward/Sprint (and engage Back when the perpendicular + /// dominates) for a few ground ticks so friction can decay the misaligned + /// component before the jump fires, and the final landing must be inside + /// the target block. + /// + [Fact] + public void AscendTemplate_IslandDiagonalFromCardinalMomentum_BrakesPerpBeforeJumpAndLandsInsideTarget() + { + // Build a small island layout at y=79 floor: + // source block (0,79,0) stands on (0,78,0) + // target block (-1,80,1) stands on (-1,79,1); approach is diagonal (-X,+Z) + // the +Z shoulder relative to the target (-1,80,2) is solid at head + // height so any over-travel along +Z bonks a wall (matches the live + // case where perpendicular momentum pushed past the target) + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -4, max: 4); + FlatWorldTestBuilder.ClearBox(world, -4, 79, -4, 4, 84, 4); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, -1, 80, 1); + FlatWorldTestBuilder.SetSolid(world, -1, 81, 2); + FlatWorldTestBuilder.SetSolid(world, -1, 80, 2); + + var ascend = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(-0.5, 81, 1.5), + MoveType = MoveType.Ascend, + ExitTransition = PathTransitionType.FinalStop, + PreserveSprint = true + }; + + var template = new AscendTemplate(ascend, null); + + // Seed pure +Z cardinal momentum at the source block center: this is + // the perpendicular axis relative to the diagonal (-X,+Z) / sqrt(2) + // heading; without the brake gate the bot would take off carrying it. + var physics = new PlayerPhysics + { + Position = new Vec3d(0.5, 80, 0.5), + DeltaMovement = new Vec3d(0.0, 0.0, 0.22), + OnGround = true, + Sprinting = true, + MovementSpeed = 0.1f, + Yaw = 0f, + Pitch = 0f + }; + + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + bool sawBrakeTick = false; + var trace = new List(); + for (int tick = 0; tick < 120; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (physics.OnGround && !input.Forward && !input.Jump) + sawBrakeTick = true; + if (tick < 24 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={tick} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + } + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True(sawBrakeTick, "expected at least one ground tick where Forward was released to decay perpendicular momentum"); + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, ascend.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + /// + /// Live regression: B->A route segment 9/41 Ascend (243.5,97,181.5)-> + /// (244.5,98,182.5) exit=Turn, fed by a Descend->PrepareJump handoff. + /// The Ascend starts mid-air with cardinal +Z momentum and yaw aimed + /// at the diagonal target. Without a post-landing completion shortcut + /// the bot lands hot (footprint partially outside target) and the + /// AscendTemplate top-level yaw smoothing toward targetYaw fights the + /// GroundedSegmentController exit-heading rotation, oscillating yaw + /// each tick until the bot drifts off the 1-block landing's edge and + /// the segment fails. With the shortcut the segment completes the + /// instant the bot lands with center inside the target column, which + /// hands off cleanly to the next template (Traverse) that snaps yaw + /// on its first tick. + /// + [Fact] + public void AscendTemplate_TurnExit_FromCarriedAirMomentum_CompletesOnTargetColumnLanding() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -4, max: 6); + FlatWorldTestBuilder.ClearBox(world, -4, 80, -4, 6, 86, 6); + // Source block (0,79,0) is the Descend's landing column; the bot + // arrives mid-jump above it. + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + // Target block (1,80,1) (NE diagonal +1y) is the Ascend's landing. + FlatWorldTestBuilder.SetSolid(world, 1, 80, 1); + // Next-segment landing column (Traverse east from target). + FlatWorldTestBuilder.SetSolid(world, 2, 80, 1); + // Walkable shoulder block beneath the bot's overshooting +Z + // footprint so the bot doesn't drop into a 2-block hole on + // landing (matches the live world geometry where a wide platform + // existed at the target's elevation). + FlatWorldTestBuilder.SetSolid(world, 1, 80, 2); + + var ascend = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 81, 1.5), + MoveType = MoveType.Ascend, + ExitTransition = PathTransitionType.Turn, + ExitHints = new PathTransitionHints( + DesiredHeadingX: 1, + DesiredHeadingZ: 0, + MinExitSpeed: 0.0, + MaxExitSpeed: 0.05, + RequireStableFooting: true, + RequireGrounded: true, + RequireJumpReady: false, + AllowAirBrake: true, + HorizonTicks: 12), + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(1.5, 81, 1.5), + End = new Location(2.5, 81, 1.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new AscendTemplate(ascend, next); + + // Seed mid-arc state mirroring the live PathDiag tick-trace at the + // first observed tick of seg9/41: bot already airborne with rising + // vy, cardinal +Z momentum from the preceding Descend, and yaw + // pointing roughly NE (the AscendTemplate snapped yaw on the + // takeoff tick). + var physics = new PlayerPhysics + { + Position = new Vec3d(0.7, 80.42, 0.92), + DeltaMovement = new Vec3d(0.0, 0.333, 0.145), + OnGround = false, + Sprinting = true, + MovementSpeed = 0.1f, + Yaw = 311f, + Pitch = 0f + }; + + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + var trace = new List(); + int elapsedTicks = 0; + for (; elapsedTicks < 80; elapsedTicks++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (elapsedTicks < 30 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={elapsedTicks} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + } + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + state == TemplateState.Complete, + $"state={state} elapsed={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True( + physics.OnGround, + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, ascend.End), + $"finalPos={finalPos} target={ascend.End}\n{string.Join('\n', trace)}"); + Assert.True( + elapsedTicks <= 30, + $"completion took {elapsedTicks} ticks; expected post-landing shortcut to fire promptly\n{string.Join('\n', trace)}"); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs b/MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs new file mode 100644 index 0000000000..5e11fe4e9e --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs @@ -0,0 +1,562 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using MinecraftClient.Mapping; +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; + +public sealed class LivePathingRegressionTests +{ + [Fact] + public void AStar_ThreeByOneRejectionLayout_WithInvalidGoalBlock_RejectsBeforeExecution() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 135, max: 148); + FlatWorldTestBuilder.ClearBox(world, 140, 80, 135, 148, 85, 140); + // Match the live harness: the raised block is reachable, but the requested goal block is not standable. + FlatWorldTestBuilder.SetSolid(world, 143, 80, 138); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + + PathResult result = finder.Calculate( + ctx, + startX: 141, + startY: 80, + startZ: 138, + new GoalBlock(144, 81, 138), + CancellationToken.None, + timeoutMs: 2000); + + Assert.Equal(PathStatus.Failed, result.Status); + 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 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 SprintJumpTemplate_LandingRecoveryIntoTurn_CompletesInsideLandingBlock() + { + 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); + FlatWorldTestBuilder.SetSolid(world, 120, 80, 111); + FlatWorldTestBuilder.SetSolid(world, 120, 81, 111); + + var segment = 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 = new Location(122.5, 80, 110.5), + End = new Location(122.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.03, true, true, false, false, 12) + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End), $"finalPos={finalPos} vel={physics.DeltaMovement}"); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + Assert.InRange(horizontalSpeed, 0.0, 0.04); + } + + [Fact] + public void AStar_LinearFlatGapFourChain_PlansThroughAllThreeJumps() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.NotEmpty(segments); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); + } + + [Fact] + public void AStar_LinearFlatGapFourChain_TagsParkourSegmentsAsDefaultProfile() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.Equal(3, segments.Count(segment => segment.MoveType == MoveType.Parkour)); + Assert.All( + segments.Where(segment => segment.MoveType == MoveType.Parkour), + segment => Assert.Equal(ParkourProfile.Default, segment.ParkourProfile)); + } + + [Fact] + public void AStar_LinearFlatGap4_DoesNotInsertRunupSetupSegments() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + int firstParkourIndex = segments.FindIndex(segment => segment.MoveType == MoveType.Parkour); + + Assert.Equal(3, firstParkourIndex); + Assert.All( + segments.Take(firstParkourIndex), + segment => + { + Assert.Equal(MoveType.Traverse, segment.MoveType); + Assert.True(segment.End.X > segment.Start.X, segment.ToString()); + }); + Assert.Equal(new Location(3.5, 80, 0.5), segments[firstParkourIndex - 1].End); + } + + [Theory] + [InlineData("linear-ascend-gap2-dy+1", 2, 1)] + [InlineData("linear-descend-gap4-dy-1", 4, -1)] + public void AStar_LinearChainCases_PlansThroughAllThreeJumps(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.NotEmpty(segments); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); + } + + [Fact] + public void AStar_LinearDescendGap2DyMinus1_DoesNotSkipIntermediateLanding() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap2-dy-1", gap: 2, deltaY: -1); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.DoesNotContain( + segments, + segment => segment.MoveType == MoveType.Parkour + && (Math.Abs(segment.End.X - segment.Start.X) + Math.Abs(segment.End.Z - segment.Start.Z)) > 3.1); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); + } + + [Theory] + [InlineData("linear-descend-gap4-dy-2", 4, -2)] + public void AStar_LinearExtendedChainCases_PlansThroughAllThreeJumps(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.NotEmpty(segments); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); + } + + [Theory] + [InlineData("linear-ascend-gap3-dy+1", 3, 1)] + [InlineData("linear-descend-gap5-dy-1", 5, -1)] + [InlineData("linear-descend-gap5-dy-2", 5, -2)] + public void AStar_LinearRejectedCases_RejectBeforeExecution(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + + Assert.Equal(PathStatus.Failed, result.Status); + Assert.Empty(PathSegmentBuilder.FromPath(result.Path)); + } + + [Theory] + [MemberData(nameof(SidewallParkourScenarioBuilder.AcceptedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] + public void AStar_SidewallAcceptedCases_PlanThroughAllThreeJumps(string scenarioId, int gap, int deltaY, int wallOffset) + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.Equal(3, segments.FindAll(segment => segment.MoveType == MoveType.Parkour).Count); + Assert.All( + segments.Where(segment => segment.MoveType == MoveType.Parkour), + segment => Assert.Equal(ParkourProfile.Sidewall, segment.ParkourProfile)); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); + } + + [Theory] + [InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)] + [InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)] + public void AStar_SidewallLongDescendStaticEntry_PrependsExplicitRunupTraverses(string scenarioId, int gap, int wallOffset) + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + scenarioId, + gap, + deltaY: -1, + wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + + int firstParkourIndex = segments.FindIndex(segment => segment.MoveType == MoveType.Parkour); + Assert.True(firstParkourIndex >= 2, string.Join('\n', segments)); + Assert.All( + segments.Take(firstParkourIndex), + segment => Assert.Equal(MoveType.Traverse, segment.MoveType)); + Assert.Equal(new Location(100.5, 80, 100.5), segments[firstParkourIndex - 1].End); + Assert.Equal(ParkourProfile.Sidewall, segments[firstParkourIndex].ParkourProfile); + } + + [Theory] + [MemberData(nameof(SidewallParkourScenarioBuilder.RejectedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] + public void AStar_SidewallRejectedCases_RejectBeforeExecution(string scenarioId, int gap, int deltaY, int wallOffset) + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + + Assert.Equal(PathStatus.Failed, result.Status); + Assert.Empty(PathSegmentBuilder.FromPath(result.Path)); + } + + [Fact] + public void AStar_LiveCoordinateLinearDescendGap3DyMinus2_PlansThroughAllThreeJumps() + { + const int baseX = 100; + const int baseY = 80; + const int baseZ = 180; + + World world = BuildLiveLinearWorld(baseX, baseY, baseZ, gap: 3, deltaY: -2, segments: 3); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + + PathResult result = finder.Calculate( + ctx, + startX: baseX, + startY: baseY, + startZ: baseZ, + new GoalBlock(115, 74, 180), + CancellationToken.None, + timeoutMs: 2000); + + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.NotEmpty(segments); + Assert.Equal(new Location(115.5, 74, 180.5), segments[^1].End); + } + + [Fact] + public void PathSegmentManager_LiveCoordinateLinearFlatGap2_CompletesWithoutReplan() + { + const int baseX = 100; + const int baseY = 80; + const int baseZ = 297; + + World world = BuildLiveLinearWorld(baseX, baseY, baseZ, gap: 2, deltaY: 0, segments: 3); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + PathResult planResult = finder.Calculate( + ctx, + startX: baseX, + startY: baseY, + startZ: baseZ, + new GoalBlock(112, 80, 297), + CancellationToken.None, + timeoutMs: 2000); + + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(baseX + 0.5, baseY, baseZ + 0.5), yaw: 270f); + var input = new MovementInput(); + var recentTrace = new Queue(); + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + + Assert.Equal(PathStatus.Success, planResult.Status); + manager.StartNavigation(new GoalBlock(112, 80, 297), planResult); + + for (int tick = 0; tick < 200 && manager.IsNavigating; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + recentTrace.Enqueue( + $"tick={tick} pos={pos} vel={physics.DeltaMovement} yaw={physics.Yaw:F1} onGround={physics.OnGround} " + + $"input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + if (recentTrace.Count > 80) + recentTrace.Dequeue(); + + if (!manager.IsNavigating) + break; + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + !manager.IsNavigating + && manager.Goal is null + && manager.ReplanCount == 0 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, new Location(112.5, 80, 297.5)), + $"replans={manager.ReplanCount} final={finalPos}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}\ntrace={string.Join('\n', recentTrace)}"); + } + + [Fact] + public void PathSegmentManager_LiveCoordinateLinearDescendGap3DyMinus2_CompletesWithoutReplan() + { + const int baseX = 100; + const int baseY = 80; + const int baseZ = 180; + + World world = BuildLiveLinearWorld(baseX, baseY, baseZ, gap: 3, deltaY: -2, segments: 3); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + PathResult planResult = finder.Calculate( + ctx, + startX: baseX, + startY: baseY, + startZ: baseZ, + new GoalBlock(115, 74, 180), + CancellationToken.None, + timeoutMs: 2000); + + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(baseX + 0.5, baseY, baseZ + 0.5), yaw: 270f); + var input = new MovementInput(); + var recentTrace = new Queue(); + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + + Assert.Equal(PathStatus.Success, planResult.Status); + manager.StartNavigation(new GoalBlock(115, 74, 180), planResult); + + 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); + recentTrace.Enqueue( + $"tick={tick} pos={pos} vel={physics.DeltaMovement} yaw={physics.Yaw:F1} onGround={physics.OnGround} " + + $"input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + if (recentTrace.Count > 100) + recentTrace.Dequeue(); + + if (!manager.IsNavigating) + break; + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.True( + !manager.IsNavigating + && manager.Goal is null + && manager.ReplanCount == 0 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, new Location(115.5, 74, 180.5)), + $"replans={manager.ReplanCount} final={finalPos}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}\ntrace={string.Join('\n', recentTrace)}"); + } + + [Fact] + public void PathSegmentManager_LinearDescendGap0DyMinus2_DoesNotTurnInPlace() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap0-dy-2", gap: 0, deltaY: -2); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + World world = scenario.BuildWorld(); + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw); + var input = new MovementInput(); + var samples = new List(); + + Assert.Equal(PathStatus.Success, planResult.Status); + manager.StartNavigation(scenario.Goal, planResult); + + for (int tick = 0; tick < scenario.MaxExecutionTicks && manager.IsNavigating; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + + physics.ApplyInput(input); + physics.Tick(world); + + samples.Add(new TurnSample(physics.Position.X, physics.Position.Y, physics.Position.Z, physics.Yaw)); + } + + Assert.True(!manager.IsNavigating && manager.Goal is null && manager.ReplanCount == 0, + $"replans={manager.ReplanCount}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}"); + int turnStalls = CountTurnStalls(samples, out string stallTrace); + Assert.True(turnStalls == 0, $"turnStalls={turnStalls}\n{stallTrace}"); + } + + private readonly record struct TurnSample(double X, double Y, double Z, float Yaw); + + private static int CountTurnStalls(IReadOnlyList samples, out string trace) + { + const int MinSamples = 4; + const double WindowMaxTravel = 0.35; + const double MinCumulativeYaw = 180.0; + const double MinPerStepYaw = 35.0; + var traces = new List(); + + if (samples.Count < MinSamples) + { + trace = string.Empty; + return 0; + } + + static double NormalizeYawDelta(float previousYaw, float currentYaw) + { + double delta = (currentYaw - previousYaw + 180.0) % 360.0 - 180.0; + return Math.Abs(delta); + } + + static double HorizontalDistance(in TurnSample a, in TurnSample b) + { + double dx = a.X - b.X; + double dz = a.Z - b.Z; + return Math.Sqrt(dx * dx + dz * dz); + } + + int count = 0; + int windowStart = 0; + while (windowStart <= samples.Count - MinSamples) + { + TurnSample baseSample = samples[windowStart]; + double cumulativeYaw = 0.0; + int largeSwings = 0; + bool matched = false; + + for (int idx = windowStart + 1; idx < samples.Count; idx++) + { + TurnSample sample = samples[idx]; + if (HorizontalDistance(baseSample, sample) > WindowMaxTravel) + break; + + double yawDelta = NormalizeYawDelta(samples[idx - 1].Yaw, sample.Yaw); + cumulativeYaw += yawDelta; + if (yawDelta >= MinPerStepYaw) + largeSwings++; + + int sampleCount = idx - windowStart + 1; + if (sampleCount >= MinSamples + && largeSwings >= MinSamples - 1 + && cumulativeYaw >= MinCumulativeYaw) + { + var windowSamples = new List(); + for (int traceIdx = windowStart; traceIdx <= idx; traceIdx++) + { + TurnSample traceSample = samples[traceIdx]; + windowSamples.Add($"#{traceIdx}=({traceSample.X:F2},{traceSample.Y:F2},{traceSample.Z:F2},{traceSample.Yaw:F1})"); + } + traces.Add( + $"start={windowStart} end={idx} base=({baseSample.X:F2},{baseSample.Y:F2},{baseSample.Z:F2},{baseSample.Yaw:F1}) " + + $"last=({sample.X:F2},{sample.Y:F2},{sample.Z:F2},{sample.Yaw:F1}) yaw={cumulativeYaw:F1}\n" + + string.Join(' ', windowSamples)); + count++; + windowStart = idx + 1; + matched = true; + break; + } + } + + if (!matched) + windowStart++; + } + + trace = string.Join('\n', traces); + return count; + } + + private static World BuildLiveLinearWorld(int baseX, int baseY, int baseZ, int gap, int deltaY, int segments) + { + int endX = baseX + 3 + ((gap + 1) * segments); + int min = Math.Min(baseX - 8, baseZ - 8); + int max = Math.Max(endX + 8, baseZ + 8); + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: min, max: max); + FlatWorldTestBuilder.ClearBox(world, baseX - 8, 1, baseZ - 2, endX + 8, baseY + 12, baseZ + 2); + + int floorY = baseY - 1; + FlatWorldTestBuilder.FillSolid(world, baseX, floorY, baseZ, baseX + 3, floorY, baseZ); + + int lastX = baseX + 3; + int lastFloorY = floorY; + for (int segment = 0; segment < segments; segment++) + { + int platformX = lastX + gap + 1; + int platformY = lastFloorY + deltaY; + FlatWorldTestBuilder.SetSolid(world, platformX, platformY, baseZ); + lastX = platformX; + lastFloorY = platformY; + } + + return world; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathExecutionLogObserverTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathExecutionLogObserverTests.cs new file mode 100644 index 0000000000..a69ae7e178 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathExecutionLogObserverTests.cs @@ -0,0 +1,39 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Telemetry; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathExecutionLogObserverTests +{ + [Fact] + public void Observer_EmitsMachineReadablePathMetricLines() + { + List lines = []; + PathExecutionLogObserver observer = new(lines.Add); + PathSegment segment = new() + { + Start = new Location(10, 64, 10), + End = new Location(11, 64, 10), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.ContinueStraight, + }; + + observer.OnNavigationStarted([segment]); + observer.OnSegmentStarted(0, 1, segment); + observer.OnSegmentCompleted(0, 1, segment, 7, new Location(11.5, 64, 10.5)); + observer.OnReplanStarted(1, new Location(11.5, 64, 10.5)); + observer.OnReplanSucceeded(1, [segment]); + observer.OnNavigationCompleted(7); + + Assert.Collection(lines, + line => Assert.Equal("[PathMetric] routeStart segments=1", line), + line => Assert.Equal("[PathMetric] segmentStart index=0 total=1 move=Traverse transition=ContinueStraight", line), + line => Assert.Equal("[PathMetric] segmentComplete index=0 total=1 move=Traverse ticks=7 x=11.50 y=64.00 z=10.50", line), + line => Assert.Equal("[PathMetric] replanStart count=1 x=11.50 y=64.00 z=10.50", line), + line => Assert.Equal("[PathMetric] replanSuccess count=1 segments=1", line), + line => Assert.Equal("[PathMetric] routeComplete totalTicks=7", line)); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs new file mode 100644 index 0000000000..23af3d7abd --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs @@ -0,0 +1,448 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using System.Reflection; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathExecutorCompletionTests +{ + [Fact] + public void Tick_ClearsMovementInput_WhenSegmentCompletes() + { + var executor = new PathExecutor(new List + { + new() + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 80, 0.5), + MoveType = MoveType.Traverse + } + }); + + var physics = new PlayerPhysics + { + Yaw = 270f, + Pitch = 0f, + OnGround = true + }; + var input = new MovementInput + { + Forward = true, + Sprint = true, + Jump = true, + Back = true + }; + var pos = new Location(1.48, 80, 0.5); + World world = FlatWorldTestBuilder.CreateStoneFloor(); + + var state = executor.Tick(pos, physics, input, world); + + Assert.Equal(PathExecutorState.Complete, state); + Assert.False(input.Forward); + Assert.False(input.Sprint); + Assert.False(input.Jump); + Assert.False(input.Back); + } + + [Fact] + public void Tick_PreservesCarryInput_WhenAdvancingIntoNextSegment() + { + var executor = new PathExecutor(new List + { + new() + { + 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 + }, + new() + { + Start = new Location(1.5, 80, 0.5), + End = new Location(4.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + } + }); + + var physics = new PlayerPhysics + { + Position = new Vec3d(1.48, 80.00, 0.50), + DeltaMovement = new Vec3d(0.1178, -0.0784, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 8); + + PathExecutorState state = executor.Tick(new Location(physics.Position.X, physics.Position.Y, physics.Position.Z), physics, input, world); + + Assert.Equal(PathExecutorState.InProgress, state); + Assert.Equal(1, executor.CurrentIndex); + Assert.True(input.Forward, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + Assert.True(input.Sprint, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + Assert.False(input.Back, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + } + + [Fact] + public void Tick_AdvanceFromParkourIntoParkour_IssuesJumpOnSameTick() + { + var executor = new PathExecutor(new List + { + new() + { + Start = new Location(0.5, 80, 0.5), + End = new Location(3.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.PrepareJump, + ExitHints = new PathTransitionHints(1, 0, 0.10, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }, + new() + { + Start = new Location(3.5, 80, 0.5), + End = new Location(6.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + } + }); + + SetCurrentTemplate( + executor, + new CompletingTemplate( + new Location(0.5, 80, 0.5), + new Location(3.5, 80, 0.5))); + + var physics = new PlayerPhysics + { + Position = new Vec3d(3.50, 80.00, 0.50), + DeltaMovement = new Vec3d(0.2200, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 10); + FlatWorldTestBuilder.ClearBox(world, 1, 79, 0, 6, 82, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 3, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 6, 79, 0); + + PathExecutorState state = executor.Tick(new Location(physics.Position.X, physics.Position.Y, physics.Position.Z), physics, input, world); + + Assert.Equal(PathExecutorState.InProgress, state); + Assert.Equal(1, executor.CurrentIndex); + Assert.True(input.Forward, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + Assert.True(input.Sprint, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + Assert.True(input.Jump, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + Assert.False(input.Back, $"input(F={input.Forward},S={input.Sprint},J={input.Jump},B={input.Back})"); + } + + [Fact] + public void Tick_CompletesStraightThreeSegmentFlatPath() + { + List segments = PathSegmentBuilder.FromPath(BuildNodes( + (100, 80, 100, MoveType.Traverse), + (101, 80, 100, MoveType.Traverse), + (102, 80, 100, MoveType.Traverse), + (103, 80, 100, MoveType.Traverse))); + + var executor = new PathExecutor(segments); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segments[0].Start, yaw: 270f); + var input = new MovementInput(); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + + PathExecutorState state = PathExecutorState.InProgress; + for (int tick = 0; tick < 260; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = executor.Tick(pos, physics, input, world); + if (state != PathExecutorState.InProgress) + break; + + physics.ApplyInput(input); + physics.Tick(world); + } + + Assert.Equal(PathExecutorState.Complete, state); + } + + [Fact] + public void Tick_ShortAcceptedPath_FromLiveSegmentZeroDriftState_CompletesWithoutFailure() + { + List segments = PathSegmentBuilder.FromPath(BuildNodes( + (100, 80, 100, MoveType.Traverse), + (101, 80, 100, MoveType.Traverse), + (102, 80, 100, MoveType.Traverse), + (103, 80, 100, MoveType.Traverse))); + + var debugLogs = new List(); + var executor = new PathExecutor(segments, debugLogs.Add); + var physics = new PlayerPhysics + { + Position = new Vec3d(101.56, 80.00, 100.74), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + + PathExecutorState state = PathExecutorState.InProgress; + for (int tick = 0; tick < 220; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = executor.Tick(pos, physics, input, world); + if (state != PathExecutorState.InProgress) + break; + + physics.ApplyInput(input); + physics.Tick(world); + } + + Assert.True(state == PathExecutorState.Complete, $"state={state}\n{string.Join('\n', debugLogs)}"); + } + + [Fact] + public void Tick_LinearDescendGap0DyMinus2_DoesNotTurnInPlace() + { + List segments = PathSegmentBuilder.FromPath(BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (2, 80, 0, MoveType.Traverse), + (3, 80, 0, MoveType.Traverse), + (4, 78, 0, MoveType.Descend), + (5, 76, 0, MoveType.Descend), + (6, 74, 0, MoveType.Descend))); + + var debugLogs = new List(); + var executor = new PathExecutor(segments, debugLogs.Add); + World world = LinearParkourScenarioBuilder.Create("linear-descend-gap0-dy-2", gap: 0, deltaY: -2).BuildWorld(); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(0.5, 80, 0.5), yaw: 270f); + var input = new MovementInput(); + var samples = new List(); + + PathExecutorState state = PathExecutorState.InProgress; + for (int tick = 0; tick < 420; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = executor.Tick(pos, physics, input, world); + if (state != PathExecutorState.InProgress) + break; + + physics.ApplyInput(input); + physics.Tick(world); + samples.Add(new TurnSample(physics.Position.X, physics.Position.Y, physics.Position.Z, physics.Yaw)); + } + + int turnStalls = CountTurnStalls(samples, out string stallTrace); + + Assert.True(state == PathExecutorState.Complete, $"state={state}\n{string.Join('\n', debugLogs)}"); + Assert.True(turnStalls == 0, $"turnStalls={turnStalls}\n{stallTrace}\n{string.Join('\n', debugLogs)}"); + } + + [Fact] + public void Tick_PlannedLinearDescendGap0DyMinus2_DoesNotTurnInPlace() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap0-dy-2", gap: 0, deltaY: -2); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(planResult.Path); + + var debugLogs = new List(); + var executor = new PathExecutor(segments, debugLogs.Add); + World world = scenario.BuildWorld(); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, yaw: scenario.StartYaw); + var input = new MovementInput(); + var samples = new List(); + + PathExecutorState state = PathExecutorState.InProgress; + for (int tick = 0; tick < scenario.MaxExecutionTicks; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = executor.Tick(pos, physics, input, world); + if (state != PathExecutorState.InProgress) + break; + + physics.ApplyInput(input); + physics.Tick(world); + samples.Add(new TurnSample(physics.Position.X, physics.Position.Y, physics.Position.Z, physics.Yaw)); + } + + int turnStalls = CountTurnStalls(samples, out string stallTrace); + string segmentTrace = string.Join('\n', segments.ConvertAll(static segment => segment.ToString())); + + Assert.Equal(PathStatus.Success, planResult.Status); + Assert.True(state == PathExecutorState.Complete, $"state={state}\nsegments:\n{segmentTrace}\n{string.Join('\n', debugLogs)}"); + Assert.True(turnStalls == 0, $"turnStalls={turnStalls}\n{stallTrace}\nsegments:\n{segmentTrace}\n{string.Join('\n', debugLogs)}"); + } + + [Fact] + public void Tick_LinearFlatGap2_CompletesWithoutFailure() + { + AssertLinearScenarioCompletes("linear-flat-gap2", gap: 2, deltaY: 0); + } + + [Fact] + public void Tick_LinearAscendGap2DyPlus1_CompletesWithoutFailure() + { + AssertLinearScenarioCompletes("linear-ascend-gap2-dy+1", gap: 2, deltaY: 1); + } + + [Fact] + public void Tick_LinearDescendGap2DyMinus1_CompletesWithoutFailure() + { + AssertLinearScenarioCompletes("linear-descend-gap2-dy-1", gap: 2, deltaY: -1); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + + return result; + } + + private readonly record struct TurnSample(double X, double Y, double Z, float Yaw); + + private sealed class CompletingTemplate(Location start, Location end) : IActionTemplate + { + public Location ExpectedStart { get; } = start; + public Location ExpectedEnd { get; } = end; + + public TemplateState Tick(Location currentPos, PlayerPhysics physics, MovementInput input, World world) + { + return TemplateState.Complete; + } + } + + private static int CountTurnStalls(IReadOnlyList samples, out string trace) + { + const int MinSamples = 4; + const double WindowMaxTravel = 0.35; + const double MinCumulativeYaw = 180.0; + const double MinPerStepYaw = 35.0; + var traces = new List(); + + if (samples.Count < MinSamples) + { + trace = string.Empty; + return 0; + } + + static double NormalizeYawDelta(float previousYaw, float currentYaw) + { + double delta = (currentYaw - previousYaw + 180.0) % 360.0 - 180.0; + return Math.Abs(delta); + } + + static double HorizontalDistance(in TurnSample a, in TurnSample b) + { + double dx = a.X - b.X; + double dz = a.Z - b.Z; + return Math.Sqrt(dx * dx + dz * dz); + } + + int count = 0; + int windowStart = 0; + while (windowStart <= samples.Count - MinSamples) + { + TurnSample baseSample = samples[windowStart]; + double cumulativeYaw = 0.0; + int largeSwings = 0; + bool matched = false; + + for (int idx = windowStart + 1; idx < samples.Count; idx++) + { + TurnSample sample = samples[idx]; + if (HorizontalDistance(baseSample, sample) > WindowMaxTravel) + break; + + double yawDelta = NormalizeYawDelta(samples[idx - 1].Yaw, sample.Yaw); + cumulativeYaw += yawDelta; + if (yawDelta >= MinPerStepYaw) + largeSwings++; + + int sampleCount = idx - windowStart + 1; + if (sampleCount >= MinSamples + && largeSwings >= MinSamples - 1 + && cumulativeYaw >= MinCumulativeYaw) + { + traces.Add( + $"start={windowStart} end={idx} base=({baseSample.X:F2},{baseSample.Y:F2},{baseSample.Z:F2},{baseSample.Yaw:F1}) " + + $"last=({sample.X:F2},{sample.Y:F2},{sample.Z:F2},{sample.Yaw:F1}) yaw={cumulativeYaw:F1}"); + count++; + windowStart = idx + 1; + matched = true; + break; + } + } + + if (!matched) + windowStart++; + } + + trace = string.Join('\n', traces); + return count; + } + + private static void SetCurrentTemplate(PathExecutor executor, IActionTemplate template) + { + FieldInfo field = typeof(PathExecutor).GetField("_currentTemplate", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("PathExecutor._currentTemplate not found"); + field.SetValue(executor, template); + } + + private static void AssertLinearScenarioCompletes(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(planResult.Path); + + var debugLogs = new List(); + var executor = new PathExecutor(segments, debugLogs.Add); + World world = scenario.BuildWorld(); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw); + var input = new MovementInput(); + + PathExecutorState state = PathExecutorState.InProgress; + for (int tick = 0; tick < scenario.MaxExecutionTicks; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = executor.Tick(pos, physics, input, world); + if (state != PathExecutorState.InProgress) + break; + + physics.ApplyInput(input); + physics.Tick(world); + } + + Location finalPos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + string segmentTrace = string.Join('\n', segments.ConvertAll(static segment => segment.ToString())); + + Assert.Equal(PathStatus.Success, planResult.Status); + Assert.True( + state == PathExecutorState.Complete, + $"scenario={scenarioId} state={state} finalPos={finalPos} vel={physics.DeltaMovement}\nsegments:\n{segmentTrace}\n{string.Join('\n', debugLogs)}"); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs new file mode 100644 index 0000000000..fa6b525a1f --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs @@ -0,0 +1,289 @@ +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Tests.Pathing.Execution.Contracts; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathPlanningContractTests +{ + [Fact] + public void Get_ManagerAcceptedAscendChain_LoadsExactPlannerContract() + { + var store = PathingContractStore.LoadFromRepositoryRoot(); + + PathingPlannerContract contract = store.GetPlanner("manager-accepted-ascend-chain"); + + Assert.Equal("manager-accepted-ascend-chain", contract.ScenarioId); + Assert.Equal(PathStatus.Success, contract.ExpectedStatus); + Assert.Equal(6, contract.Segments.Count); + + PathingPlannerSegmentContract firstSegment = contract.Segments[0]; + Assert.Equal(MoveType.Diagonal, firstSegment.MoveType); + Assert.Equal(new PathingBlock(171, 80, 160), firstSegment.StartBlock); + Assert.Equal(new PathingBlock(172, 80, 161), firstSegment.EndBlock); + + PathingPlannerSegmentContract lastSegment = contract.Segments[5]; + Assert.Equal(MoveType.Ascend, lastSegment.MoveType); + Assert.Equal(new PathingBlock(176, 82, 162), lastSegment.StartBlock); + Assert.Equal(new PathingBlock(177, 83, 162), lastSegment.EndBlock); + } + + [Fact] + public void Get_ManagerAcceptedAscendChain_LoadsTimingScaffoldShape() + { + var store = PathingContractStore.LoadFromRepositoryRoot(); + + PathingTimingBudget timing = store.GetTiming("manager-accepted-ascend-chain"); + + Assert.Equal("manager-accepted-ascend-chain", timing.ScenarioId); + Assert.Equal(6, timing.Segments.Count); + Assert.True(timing.ExpectedTotalTicks <= timing.MaxTotalTicks); + + PathingSegmentTimingBudget firstSegment = timing.Segments[0]; + Assert.Equal(MoveType.Diagonal, firstSegment.MoveType); + Assert.True(firstSegment.ExpectedTicks <= firstSegment.MaxTicks); + + foreach (PathingSegmentTimingBudget segment in timing.Segments) + { + Assert.True(segment.ExpectedTicks <= segment.MaxTicks); + } + } + + [Fact] + public void LoadFromJson_RejectsTimingSegment_WhenExpectedExceedsMax() + { + const string plannerJson = """ +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Diagonal", + "startBlock": { "x": 171, "y": 80, "z": 160 }, + "endBlock": { "x": 172, "y": 80, "z": 161 } + } + ] + } +] +"""; + const string timingJson = """ +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedTotalTicks": 1, + "maxTotalTicks": 1, + "segments": [ + { "moveType": "Diagonal", "expectedTicks": 2, "maxTicks": 1 } + ] + } +] +"""; + + InvalidDataException error = Assert.Throws( + () => PathingContractStore.LoadFromJson(plannerJson, timingJson)); + 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( + () => PathingContractStore.LoadFromJson(plannerJson, timingJson)); + Assert.Contains("totals-mismatch", error.Message); + Assert.Contains("ExpectedTotalTicks mismatch", error.Message); + } + + [Fact] + public void LoadFromJson_Rejects_WhenPlannerAndTimingScenarioSetsMismatch() + { + const string plannerJson = """ +[ + { + "scenarioId": "planner-only", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { "x": 0, "y": 80, "z": 0 }, + "endBlock": { "x": 1, "y": 80, "z": 0 } + } + ] + } +] +"""; + const string timingJson = """ +[ + { + "scenarioId": "timing-only", + "expectedTotalTicks": 1, + "maxTotalTicks": 1, + "segments": [ + { "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 } + ] + } +] +"""; + + InvalidDataException error = Assert.Throws( + () => PathingContractStore.LoadFromJson(plannerJson, timingJson)); + Assert.Contains("planner-only", error.Message); + Assert.Contains("timing-only", error.Message); + } + + [Fact] + public void LoadFromJson_RejectsDuplicatePlannerScenarioId() + { + const string plannerJson = """ +[ + { + "scenarioId": "dup", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { "x": 0, "y": 80, "z": 0 }, + "endBlock": { "x": 1, "y": 80, "z": 0 } + } + ] + }, + { + "scenarioId": "dup", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { "x": 1, "y": 80, "z": 0 }, + "endBlock": { "x": 2, "y": 80, "z": 0 } + } + ] + } +] +"""; + const string timingJson = """ +[ + { + "scenarioId": "dup", + "expectedTotalTicks": 1, + "maxTotalTicks": 1, + "segments": [ + { "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 } + ] + } +] +"""; + + InvalidDataException error = Assert.Throws( + () => PathingContractStore.LoadFromJson(plannerJson, timingJson)); + Assert.Contains("Duplicate planner contract scenario id", error.Message); + } + + [Fact] + public void LoadFromJson_Rejects_WhenPlannerAndTimingMoveSequenceDiffer() + { + const string plannerJson = """ +[ + { + "scenarioId": "sequence-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": "sequence-mismatch", + "expectedTotalTicks": 2, + "maxTotalTicks": 2, + "segments": [ + { "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 }, + { "moveType": "Diagonal", "expectedTicks": 1, "maxTicks": 1 } + ] + } +] +"""; + + InvalidDataException error = Assert.Throws( + () => PathingContractStore.LoadFromJson(plannerJson, timingJson)); + Assert.Contains("sequence-mismatch", error.Message); + Assert.Contains("segment 1", error.Message); + } + + [Theory] + [InlineData("manager-accepted-ascend-chain")] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + [InlineData("rejected-3x1-invalid-goal")] + [InlineData("repeated-cardinal-parkour-chain")] + [InlineData("repeated-diagonal-parkour-chain")] + [InlineData("obstructed-parkour-l-turns")] + [InlineData("vertical-jump-mix")] + [InlineData("diagonal-vertical-mix")] + [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); + + PathingContractAssert.PlannerMatches( + PathingContractStore.LoadFromRepositoryRoot().GetPlanner(scenarioId), + PathSegmentBuilder.FromPath(planResult.Path), + planResult); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs new file mode 100644 index 0000000000..07bd9ee069 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathSegmentBuilderTests +{ + [Fact] + public void FromPath_AnnotatesStraightTraverse_AsContinueStraight() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (2, 80, 0, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.ContinueStraight, segments[0].ExitTransition); + Assert.True(segments[0].PreserveSprint); + } + + [Fact] + public void FromPath_AnnotatesOrthogonalTraverse_AsTurn() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (1, 80, 1, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition); + Assert.False(segments[0].PreserveSprint); + } + + [Fact] + public void FromPath_AnnotatesTraverseIntoParkour_AsPrepareJump() + { + var nodes = BuildNodes( + (120, 80, 110, MoveType.Traverse), + (121, 80, 110, MoveType.Traverse), + (123, 80, 110, MoveType.Parkour)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition); + Assert.True(segments[0].PreserveSprint); + } + + [Fact] + public void FromPath_DescendIntoTurningDescend_UsesLandingRecoveryHints() + { + // Regression: a Descend that lands and immediately steps into a + // perpendicular Descend (different heading) used to receive the + // turning-branch hints with RequireStableFooting=true. That gate forces + // GroundedSegmentController to wait for IsSettledOnTargetBlock, which + // takes ~3 seconds while residual jump momentum decays. The + // LandingRecovery branch (RequireStableFooting=false) lets the + // ShouldComplete shortcut fire as soon as the bot's footprint is + // inside the landing block. + var nodes = BuildNodes( + (255, 137, 220, MoveType.Traverse), + (256, 134, 219, MoveType.Descend), + (256, 132, 217, MoveType.Descend)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.LandingRecovery, segments[0].ExitTransition); + Assert.False(segments[0].ExitHints.RequireStableFooting); + } + + [Fact] + public void FromPath_TraverseIntoTurningTraverse_StillUsesTurnHints() + { + // Sanity guard: ordinary Traverse → turning-Traverse must still use the + // turning branch (StableFooting=true) — only Descend/Parkour/Fall + // sources should bypass it. + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (1, 80, 1, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition); + Assert.True(segments[0].ExitHints.RequireStableFooting); + } + + [Fact] + public void FromPath_CopiesParkourProfile_ToRuntimeSegment() + { + var start = new PathNode(100, 80, 100); + var end = new PathNode(99, 80, 102) + { + MoveUsed = MoveType.Parkour, + ParkourProfile = ParkourProfile.Sidewall + }; + + List segments = PathSegmentBuilder.FromPath([start, end]); + + Assert.Single(segments); + Assert.Equal(ParkourProfile.Sidewall, segments[0].ParkourProfile); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + return result; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs new file mode 100644 index 0000000000..8b4e31cda8 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs @@ -0,0 +1,378 @@ +using MinecraftClient.Mapping; +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; + +public sealed class PathSegmentManagerTests +{ + [Fact] + public void Tick_AcceptedAscendChain_FromSterileStart_CompletesWithoutReplan() + { + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLog: debugLogs.Add, infoLog: infoLogs.Add); + var goal = new GoalBlock(177, 83, 162); + var path = BuildNodes( + (171, 80, 160, MoveType.Traverse), + (172, 80, 161, MoveType.Diagonal), + (173, 80, 162, MoveType.Diagonal), + (174, 80, 162, MoveType.Traverse), + (175, 81, 162, MoveType.Ascend), + (176, 82, 162, MoveType.Ascend), + (177, 83, 162, MoveType.Ascend)); + var result = new PathResult(PathStatus.Success, path, nodesExplored: 7, elapsedMs: 1); + + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 158, max: 180); + FlatWorldTestBuilder.ClearBox(world, 170, 80, 160, 178, 85, 168); + FlatWorldTestBuilder.SetSolid(world, 175, 80, 162); + FlatWorldTestBuilder.SetSolid(world, 176, 81, 162); + FlatWorldTestBuilder.SetSolid(world, 177, 82, 162); + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(171.5, 80, 160.5), yaw: 315f); + var input = new MovementInput(); + var recentTrace = new Queue(); + + manager.StartNavigation(goal, result); + + for (int tick = 0; tick < 420 && manager.IsNavigating; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + recentTrace.Enqueue( + $"tick={tick} pos={pos} vel={physics.DeltaMovement} onGround={physics.OnGround} yaw={physics.Yaw:F1} input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + if (recentTrace.Count > 40) + recentTrace.Dequeue(); + 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)}\ntrace={string.Join('\n', recentTrace)}"); + } + + [Fact] + public void Tick_AcceptedDescendStaircase_FromSterileStart_CompletesWithoutReplan() + { + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLog: debugLogs.Add, infoLog: infoLogs.Add); + var goal = new GoalBlock(367, 80, 360); + var path = BuildNodes( + (362, 85, 360, MoveType.Traverse), + (364, 83, 360, MoveType.Descend), + (366, 81, 360, MoveType.Descend), + (367, 80, 360, MoveType.Descend)); + var result = new PathResult(PathStatus.Success, path, nodesExplored: 4, elapsedMs: 1); + + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 358, max: 369); + FlatWorldTestBuilder.ClearBox(world, 360, 79, 358, 369, 85, 362); + FlatWorldTestBuilder.FillSolid(world, 362, 84, 359, 362, 84, 361); + FlatWorldTestBuilder.FillSolid(world, 363, 83, 359, 363, 83, 361); + FlatWorldTestBuilder.FillSolid(world, 364, 82, 359, 364, 82, 361); + FlatWorldTestBuilder.FillSolid(world, 365, 81, 359, 365, 81, 361); + FlatWorldTestBuilder.FillSolid(world, 366, 80, 359, 366, 80, 361); + FlatWorldTestBuilder.FillSolid(world, 367, 79, 359, 367, 79, 361); + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(362.5, 85, 360.5), yaw: 270f); + var input = new MovementInput(); + var recentTrace = new Queue(); + + manager.StartNavigation(goal, result); + + for (int tick = 0; tick < 420 && manager.IsNavigating; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + recentTrace.Enqueue( + $"tick={tick} pos={pos} vel={physics.DeltaMovement} onGround={physics.OnGround} yaw={physics.Yaw:F1} input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + if (recentTrace.Count > 40) + recentTrace.Dequeue(); + 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)}\ntrace={string.Join('\n', recentTrace)}"); + } + + [Fact] + public void Tick_CompletesNavigation_WhenReplanStartsInsideGoalBlock() + { + var infoLogs = new List(); + var manager = new PathSegmentManager(infoLog: infoLogs.Add); + var goal = new GoalBlock(1, 80, 0); + var path = new[] + { + new PathNode(0, 80, 0), + new PathNode(1, 80, 0) { MoveUsed = MoveType.Traverse } + }; + var result = new PathResult(PathStatus.Success, path, nodesExplored: 2, elapsedMs: 1); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 4); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.10, 80.0, 0.5), + DeltaMovement = Vec3d.Zero, + OnGround = false, + MovementSpeed = 0.1f, + Yaw = 270f + }; + var input = new MovementInput(); + + manager.StartNavigation(goal, result); + + for (int tick = 0; tick < 60 && manager.IsNavigating; tick++) + { + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + } + + Assert.False(manager.IsNavigating); + Assert.Null(manager.Goal); + Assert.Equal(1, manager.ReplanCount); + } + + [Fact] + public void Tick_ShortAcceptedPath_FromSterileStart_CompletesWithoutIncrementingReplanCount() + { + var infoLogs = new List(); + var manager = new PathSegmentManager(infoLog: infoLogs.Add); + var goal = new GoalBlock(103, 80, 100); + var path = new[] + { + new PathNode(100, 80, 100), + new PathNode(101, 80, 100) { MoveUsed = MoveType.Traverse }, + new PathNode(102, 80, 100) { MoveUsed = MoveType.Traverse }, + new PathNode(103, 80, 100) { MoveUsed = MoveType.Traverse } + }; + var result = new PathResult(PathStatus.Success, path, nodesExplored: 4, elapsedMs: 1); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(new Location(100.5, 80, 100.5), yaw: 270f); + var input = new MovementInput(); + + manager.StartNavigation(goal, result); + + for (int tick = 0; tick < 260 && 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.False(manager.IsNavigating); + Assert.Equal(0, manager.ReplanCount); + } + + [Fact] + public void Tick_ShortAcceptedPath_FromLiveSegmentZeroDriftState_CompletesWithoutReplan() + { + var debugLogs = new List(); + var infoLogs = new List(); + var manager = new PathSegmentManager(debugLog: debugLogs.Add, infoLog: infoLogs.Add); + var goal = new GoalBlock(103, 80, 100); + var path = new[] + { + new PathNode(100, 80, 100), + new PathNode(101, 80, 100) { MoveUsed = MoveType.Traverse }, + new PathNode(102, 80, 100) { MoveUsed = MoveType.Traverse }, + new PathNode(103, 80, 100) { MoveUsed = MoveType.Traverse } + }; + var result = new PathResult(PathStatus.Success, path, nodesExplored: 4, elapsedMs: 1); + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + var physics = new PlayerPhysics + { + Position = new Vec3d(101.56, 80.00, 100.74), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + + manager.StartNavigation(goal, result); + + for (int tick = 0; tick < 260 && 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)}"); + } + + [Theory] + [MemberData(nameof(SidewallParkourScenarioBuilder.AcceptedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] + public void Tick_SidewallAcceptedCases_CompletesWithoutReplan(string scenarioId, int gap, int deltaY, int wallOffset) + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + Location goalLocation = new(scenario.Goal.X + 0.5, scenario.Goal.Y, scenario.Goal.Z + 0.5); + + Assert.True( + result.Completed + && result.ReplanCount == 0 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(result.FinalPosition, goalLocation), + $"scenario={scenarioId} completed={result.Completed} replans={result.ReplanCount} final={result.FinalPosition} " + + $"goal={goalLocation} planStatus={result.PlanResult.Status}\ninfo={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + } + + [Theory] + [MemberData(nameof(LinearParkourScenarioBuilder.AcceptedCases), MemberType = typeof(LinearParkourScenarioBuilder))] + public void Tick_LinearAcceptedChain_CompletesWithoutReplan(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + World world = scenario.BuildWorld(); + var debugLogs = new List(); + var infoLogs = new List(); + var observer = new RecordingPathExecutionObserver(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add, observer); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw); + var input = new MovementInput(); + var recentTrace = new Queue(); + + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + manager.StartNavigation(scenario.Goal, planResult); + + for (int tick = 0; tick < scenario.MaxExecutionTicks && manager.IsNavigating; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + manager.Tick(pos, physics, input, world); + recentTrace.Enqueue( + $"tick={tick} pos={pos} vel={physics.DeltaMovement} yaw={physics.Yaw:F1} onGround={physics.OnGround} " + + $"input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})"); + if (recentTrace.Count > 60) + recentTrace.Dequeue(); + + if (!manager.IsNavigating) + break; + + physics.ApplyInput(input); + physics.Tick(world); + } + + Location finalPosition = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + Location goalLocation = new(scenario.Goal.X + 0.5, scenario.Goal.Y, scenario.Goal.Z + 0.5); + + Assert.True( + !manager.IsNavigating + && manager.Goal is null + && observer.ReplanCount == 0 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPosition, goalLocation), + $"scenario={scenarioId} completed={!manager.IsNavigating && manager.Goal is null} replans={observer.ReplanCount} final={finalPosition} " + + $"goal={goalLocation} planStatus={planResult.Status}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}\ntrace={string.Join('\n', recentTrace)}"); + } + + [Fact] + public void Tick_LinearFlatGap2_CompletesWithoutReplan() + { + AssertLinearScenarioCompletesWithoutReplan("linear-flat-gap2", gap: 2, deltaY: 0); + } + + [Fact] + public void Tick_LinearAscendGap2DyPlus1_CompletesWithoutReplan() + { + AssertLinearScenarioCompletesWithoutReplan("linear-ascend-gap2-dy+1", gap: 2, deltaY: 1); + } + + [Fact] + public void Tick_LinearAscendGap3DyPlus1_CompletesWithoutReplan() + { + AssertLinearScenarioRejected("linear-ascend-gap3-dy+1", gap: 3, deltaY: 1); + } + + [Fact] + public void Tick_LinearDescendGap2DyMinus1_CompletesWithoutReplan() + { + AssertLinearScenarioCompletesWithoutReplan("linear-descend-gap2-dy-1", gap: 2, deltaY: -1); + } + + [Fact] + public void Tick_LinearDescendGap3DyMinus2_CompletesWithoutReplan() + { + AssertLinearScenarioCompletesWithoutReplan("linear-descend-gap3-dy-2", gap: 3, deltaY: -2); + } + + [Fact] + public void Tick_LinearDescendGap5DyMinus1_CompletesWithoutReplan() + { + AssertLinearScenarioRejected("linear-descend-gap5-dy-1", gap: 5, deltaY: -1); + } + + [Fact] + public void Tick_LinearDescendGap5DyMinus2_CompletesWithoutReplan() + { + AssertLinearScenarioRejected("linear-descend-gap5-dy-2", gap: 5, deltaY: -2); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + + return result; + } + + private static void AssertLinearScenarioCompletesWithoutReplan(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + Location goalLocation = new(scenario.Goal.X + 0.5, scenario.Goal.Y, scenario.Goal.Z + 0.5); + + Assert.True( + result.Completed + && result.ReplanCount == 0 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(result.FinalPosition, goalLocation), + $"scenario={scenarioId} completed={result.Completed} replans={result.ReplanCount} final={result.FinalPosition} " + + $"goal={goalLocation} planStatus={result.PlanResult.Status}\ninfo={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + } + + private static void AssertLinearScenarioRejected(string scenarioId, int gap, int deltaY) + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create(scenarioId, gap, deltaY); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + var infoLogs = new List(); + var manager = new PathSegmentManager(infoLog: infoLogs.Add); + + manager.StartNavigation(scenario.Goal, result); + + Assert.True( + result.Status == PathStatus.Failed + && !manager.IsNavigating + && manager.Goal is null + && manager.ReplanCount == 0 + && infoLogs.Exists(log => log.Contains("no path found", StringComparison.OrdinalIgnoreCase)), + $"scenario={scenarioId} planStatus={result.Status}\npath={string.Join('\n', PathSegmentBuilder.FromPath(result.Path))}\ninfo={string.Join('\n', infoLogs)}"); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs new file mode 100644 index 0000000000..383d3d73cd --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs @@ -0,0 +1,69 @@ +using MinecraftClient.Tests.Pathing.Execution.Contracts; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathTimingContractTests +{ + [Fact] + public void Run_ManagerAcceptedAscendChain_CapturesPerSegmentTicks_AndZeroReplan() + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get("manager-accepted-ascend-chain"); + + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + Assert.Equal(0, result.ReplanCount); + Assert.True(result.Completed); + Assert.Equal(6, result.SegmentRuns.Count); + Assert.All(result.SegmentRuns, run => Assert.True(run.ElapsedTicks > 0)); + } + + [Theory] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + public void Scenario_ExecutionStaysWithinTimingBudget(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.LoadFromRepositoryRoot().GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); + } + + [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_ExecutionStaysWithinBudget(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.LoadFromRepositoryRoot().GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); + } + + [Theory] + [InlineData("same-move-straight-traverse-chain")] + [InlineData("same-move-diagonal-chain")] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + [InlineData("same-move-aligned-parkour-chain")] + [InlineData("mixed-traverse-turn-parkour-turn-traverse")] + [InlineData("mixed-diagonal-ascend-traverse-descend")] + [InlineData("mixed-traverse-ascend-parkour-descend")] + [InlineData("turn-density-alternating-traverse-diagonal-chain")] + [InlineData("speed-carry-repeated-traverse-ascend")] + [InlineData("speed-carry-repeated-traverse-descend")] + [InlineData("speed-carry-repeated-traverse-parkour")] + public void LongRoute_ExecutionStaysWithinBudget(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.LoadFromRepositoryRoot().GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs new file mode 100644 index 0000000000..b2e4675864 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathTransitionHintsTests +{ + [Fact] + public void FromPath_AssignsTurnHints_WhenNextSegmentChangesHeading() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (1, 80, 1, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition); + Assert.True(hints.RequireStableFooting); + Assert.True(hints.RequireGrounded); + Assert.Equal(0, hints.DesiredHeadingX); + Assert.Equal(1, hints.DesiredHeadingZ); + Assert.InRange(hints.MaxExitSpeed, 0.0, 0.05); + } + + [Fact] + public void FromPath_AssignsJumpReadyHints_WhenNextSegmentIsParkour() + { + var nodes = BuildNodes( + (120, 80, 110, MoveType.Traverse), + (121, 80, 110, MoveType.Traverse), + (123, 80, 110, MoveType.Parkour)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition); + Assert.True(hints.RequireJumpReady); + Assert.False(hints.RequireStableFooting); + Assert.Equal(1, hints.DesiredHeadingX); + Assert.Equal(0, hints.DesiredHeadingZ); + Assert.True(hints.MinExitSpeed >= 0.10, $"MinExitSpeed={hints.MinExitSpeed}"); + } + + [Fact] + public void FromPath_AssignsZeroRunUpSpeedHints_WhenNextSegmentIsAscend() + { + var nodes = BuildNodes( + (174, 80, 162, MoveType.Traverse), + (175, 80, 162, MoveType.Traverse), + (176, 81, 162, MoveType.Ascend)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition); + Assert.True(hints.RequireJumpReady); + Assert.Equal(0.0, hints.MinExitSpeed); + } + + [Fact] + public void FromPath_AssignsPreciseStopHints_WhenSegmentIsFinalStop() + { + var nodes = BuildNodes( + (10, 80, 10, MoveType.Traverse), + (11, 80, 10, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.FinalStop, segments[0].ExitTransition); + Assert.True(hints.RequireStableFooting); + Assert.True(hints.RequireGrounded); + Assert.InRange(hints.MaxExitSpeed, 0.0, 0.02); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + + return result; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs b/MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs new file mode 100644 index 0000000000..4f579995c8 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs @@ -0,0 +1,65 @@ +using MinecraftClient.Pathing.Core; +using Xunit; +using Xunit.Abstractions; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathingContractBootstrapTests +{ + private readonly ITestOutputHelper _output; + + public PathingContractBootstrapTests(ITestOutputHelper output) => _output = output; + + [Theory] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + [InlineData("rejected-3x1-invalid-goal")] + public void PrintShortRouteContractFragments(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + if (planResult.Status == PathStatus.Success) + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); + } + + [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 PrintJumpComboContractFragments(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + if (planResult.Status == PathStatus.Success) + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); + } + + [Theory] + [InlineData("same-move-straight-traverse-chain")] + [InlineData("same-move-diagonal-chain")] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + [InlineData("same-move-aligned-parkour-chain")] + [InlineData("mixed-traverse-turn-parkour-turn-traverse")] + [InlineData("mixed-diagonal-ascend-traverse-descend")] + [InlineData("mixed-traverse-ascend-parkour-descend")] + [InlineData("turn-density-alternating-traverse-diagonal-chain")] + [InlineData("speed-carry-repeated-traverse-ascend")] + [InlineData("speed-carry-repeated-traverse-descend")] + [InlineData("speed-carry-repeated-traverse-parkour")] + public void PrintLongRouteContractFragments(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + if (planResult.Status == PathStatus.Success) + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs b/MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs new file mode 100644 index 0000000000..948e10be59 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs @@ -0,0 +1,59 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public static class LinearParkourScenarioBuilder +{ + private const int SegmentCount = 3; + private const int BaseY = 80; + private const int FloorY = BaseY - 1; + + public static IEnumerable AcceptedCases() + { + yield return ["linear-ascend-gap1-dy+1", 1, 1]; + yield return ["linear-ascend-gap2-dy+1", 2, 1]; + yield return ["linear-descend-gap2-dy-2", 2, -2]; + yield return ["linear-descend-gap3-dy-1", 3, -1]; + yield return ["linear-descend-gap4-dy-1", 4, -1]; + yield return ["linear-flat-gap1", 1, 0]; + yield return ["linear-flat-gap4", 4, 0]; + } + + internal static PathingExecutionScenario Create(string scenarioId, int gap, int deltaY, int maxExecutionTicks = 600) + { + int endX = 3 + ((gap + 1) * SegmentCount); + int endFloorY = FloorY + (deltaY * SegmentCount); + + return new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = () => BuildWorld(gap, deltaY), + Start = new Location(0.5, BaseY, 0.5), + Goal = new GoalBlock(endX, endFloorY + 1, 0), + StartYaw = 270f, + MaxExecutionTicks = maxExecutionTicks, + }; + } + + internal static World BuildWorld(int gap, int deltaY) + { + int endX = 3 + ((gap + 1) * SegmentCount); + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: -8, max: endX + 8); + FlatWorldTestBuilder.ClearBox(world, -8, 1, -2, endX + 8, BaseY + 12, 2); + FlatWorldTestBuilder.FillSolid(world, 0, FloorY, 0, 3, FloorY, 0); + + int lastX = 3; + int lastFloorY = FloorY; + for (int segment = 0; segment < SegmentCount; segment++) + { + int platformX = lastX + gap + 1; + int platformY = lastFloorY + deltaY; + FlatWorldTestBuilder.SetSolid(world, platformX, platformY, 0); + lastX = platformX; + lastFloorY = platformY; + } + + return world; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs b/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs new file mode 100644 index 0000000000..e404a3f673 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs @@ -0,0 +1,15 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathingExecutionScenario +{ + public required string Id { get; init; } + public required Func BuildWorld { get; init; } + public required Location Start { get; init; } + public required GoalBlock Goal { get; init; } + public required float StartYaw { get; init; } + public int InitialMomentumTicks { get; init; } + public required int MaxExecutionTicks { get; init; } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs b/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs new file mode 100644 index 0000000000..f88ac0db71 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs @@ -0,0 +1,439 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingExecutionScenarioCatalog +{ + internal static PathingExecutionScenario Get(string scenarioId) => scenarioId switch + { + "manager-accepted-ascend-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildManagerAcceptedAscendChain, + Start = new Location(171.5, 80, 160.5), + Goal = new GoalBlock(177, 83, 162), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "same-move-ascend-staircase" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveAscendStaircase, + Start = new Location(340.5, 80, 340.5), + Goal = new GoalBlock(345, 85, 340), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "same-move-descend-staircase" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveDescendStaircase, + Start = new Location(362.5, 85, 360.5), + Goal = new GoalBlock(367, 80, 360), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "rejected-3x1-invalid-goal" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRejectedThreeByOneInvalidGoal, + Start = new Location(141.5, 80, 138.5), + Goal = new GoalBlock(144, 81, 138), + StartYaw = 270f, + MaxExecutionTicks = 80 + }, + "repeated-cardinal-parkour-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRepeatedCardinalParkourChain, + Start = new Location(580.5, 80, 580.5), + Goal = new GoalBlock(588, 80, 580), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "repeated-diagonal-parkour-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRepeatedDiagonalParkourChain, + Start = new Location(600.5, 80, 600.5), + Goal = new GoalBlock(606, 80, 606), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "obstructed-parkour-l-turns" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildObstructedParkourLTurns, + Start = new Location(620.5, 80, 620.5), + Goal = new GoalBlock(626, 80, 622), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "vertical-jump-mix" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildVerticalJumpMix, + Start = new Location(640.5, 80, 620.5), + Goal = new GoalBlock(648, 80, 620), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "diagonal-vertical-mix" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildDiagonalVerticalMix, + Start = new Location(680.5, 80, 620.5), + Goal = new GoalBlock(684, 80, 624), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "same-move-straight-traverse-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveStraightTraverseChain, + Start = new Location(300.5, 80, 300.5), + Goal = new GoalBlock(312, 80, 300), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "same-move-diagonal-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveDiagonalChain, + Start = new Location(320.5, 80, 320.5), + Goal = new GoalBlock(327, 80, 327), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "same-move-aligned-parkour-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveAlignedParkourChain, + Start = new Location(380.5, 80, 380.5), + Goal = new GoalBlock(388, 80, 380), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "mixed-traverse-turn-parkour-turn-traverse" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildMixedTraverseTurnParkourTurnTraverse, + Start = new Location(400.5, 80, 400.5), + Goal = new GoalBlock(408, 80, 404), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "mixed-diagonal-ascend-traverse-descend" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildMixedDiagonalAscendTraverseDescend, + Start = new Location(420.5, 80, 420.5), + Goal = new GoalBlock(428, 80, 422), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "mixed-traverse-ascend-parkour-descend" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildMixedTraverseAscendParkourDescend, + Start = new Location(440.5, 80, 440.5), + Goal = new GoalBlock(448, 80, 440), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "turn-density-alternating-traverse-diagonal-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildTurnDensityAlternatingTraverseDiagonalChain, + Start = new Location(460.5, 80, 460.5), + Goal = new GoalBlock(466, 80, 466), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "speed-carry-repeated-traverse-ascend" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSpeedCarryRepeatedTraverseAscend, + Start = new Location(480.5, 80, 480.5), + Goal = new GoalBlock(488, 84, 480), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "speed-carry-repeated-traverse-descend" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSpeedCarryRepeatedTraverseDescend, + Start = new Location(500.5, 83, 500.5), + Goal = new GoalBlock(507, 80, 500), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "speed-carry-repeated-traverse-parkour" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSpeedCarryRepeatedTraverseParkour, + Start = new Location(520.5, 80, 520.5), + Goal = new GoalBlock(529, 80, 520), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + _ => throw new ArgumentOutOfRangeException(nameof(scenarioId), scenarioId, null) + }; + + private static World BuildManagerAcceptedAscendChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 158, max: 180); + FlatWorldTestBuilder.ClearBox(world, 170, 80, 160, 178, 85, 168); + FlatWorldTestBuilder.SetSolid(world, 175, 80, 162); + FlatWorldTestBuilder.SetSolid(world, 176, 81, 162); + FlatWorldTestBuilder.SetSolid(world, 177, 82, 162); + return world; + } + + private static World BuildSameMoveAscendStaircase() + { + 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); + FlatWorldTestBuilder.FillSolid(world, 344, 83, 339, 344, 83, 341); + FlatWorldTestBuilder.FillSolid(world, 345, 84, 339, 345, 84, 341); + return world; + } + + private static World BuildSameMoveDescendStaircase() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 358, max: 369); + FlatWorldTestBuilder.ClearBox(world, 360, 79, 358, 369, 85, 362); + FlatWorldTestBuilder.FillSolid(world, 362, 84, 359, 362, 84, 361); + FlatWorldTestBuilder.FillSolid(world, 363, 83, 359, 363, 83, 361); + FlatWorldTestBuilder.FillSolid(world, 364, 82, 359, 364, 82, 361); + FlatWorldTestBuilder.FillSolid(world, 365, 81, 359, 365, 81, 361); + FlatWorldTestBuilder.FillSolid(world, 366, 80, 359, 366, 80, 361); + FlatWorldTestBuilder.FillSolid(world, 367, 79, 359, 367, 79, 361); + return world; + } + + private static World BuildRejectedThreeByOneInvalidGoal() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 135, max: 148); + FlatWorldTestBuilder.ClearBox(world, 140, 80, 135, 148, 85, 140); + FlatWorldTestBuilder.SetSolid(world, 143, 80, 138); + return world; + } + + private static World BuildRepeatedCardinalParkourChain() + { + 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); + return world; + } + + private static World BuildRepeatedDiagonalParkourChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 598, max: 608); + FlatWorldTestBuilder.ClearBox(world, 598, 79, 598, 608, 90, 608); + FlatWorldTestBuilder.SetSolid(world, 600, 79, 600); + FlatWorldTestBuilder.SetSolid(world, 602, 79, 602); + FlatWorldTestBuilder.SetSolid(world, 604, 79, 604); + FlatWorldTestBuilder.SetSolid(world, 606, 79, 606); + return world; + } + + private static World BuildObstructedParkourLTurns() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 618, max: 628); + FlatWorldTestBuilder.ClearBox(world, 618, 79, 618, 628, 90, 624); + FlatWorldTestBuilder.SetSolid(world, 620, 79, 620); + FlatWorldTestBuilder.SetSolid(world, 622, 79, 620); + FlatWorldTestBuilder.SetSolid(world, 622, 79, 621); + FlatWorldTestBuilder.SetSolid(world, 624, 79, 621); + FlatWorldTestBuilder.SetSolid(world, 624, 79, 622); + FlatWorldTestBuilder.SetSolid(world, 626, 79, 622); + FlatWorldTestBuilder.SetSolid(world, 620, 80, 621); + FlatWorldTestBuilder.SetSolid(world, 620, 81, 621); + FlatWorldTestBuilder.SetSolid(world, 622, 80, 622); + FlatWorldTestBuilder.SetSolid(world, 622, 81, 622); + return world; + } + + private static World BuildVerticalJumpMix() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 618, max: 650); + FlatWorldTestBuilder.ClearBox(world, 638, 79, 618, 650, 80, 622); + FlatWorldTestBuilder.ClearBox(world, 638, 81, 618, 650, 92, 622); + FlatWorldTestBuilder.SetSolid(world, 640, 79, 620); + FlatWorldTestBuilder.SetSolid(world, 642, 80, 620); + FlatWorldTestBuilder.SetSolid(world, 644, 79, 620); + FlatWorldTestBuilder.SetSolid(world, 646, 80, 620); + FlatWorldTestBuilder.SetSolid(world, 648, 79, 620); + return world; + } + + private static World BuildDiagonalVerticalMix() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 618, max: 686); + FlatWorldTestBuilder.ClearBox(world, 678, 79, 618, 686, 80, 626); + FlatWorldTestBuilder.ClearBox(world, 678, 81, 618, 686, 92, 626); + FlatWorldTestBuilder.SetSolid(world, 680, 79, 620); + FlatWorldTestBuilder.SetSolid(world, 681, 80, 621); + FlatWorldTestBuilder.SetSolid(world, 682, 79, 622); + FlatWorldTestBuilder.SetSolid(world, 683, 80, 623); + FlatWorldTestBuilder.SetSolid(world, 684, 79, 624); + return world; + } + + private static World BuildSameMoveStraightTraverseChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 298, max: 314); + FlatWorldTestBuilder.ClearBox(world, 298, 79, 298, 314, 90, 302); + FlatWorldTestBuilder.FillSolid(world, 300, 79, 300, 312, 79, 300); + return world; + } + + private static World BuildSameMoveDiagonalChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 318, max: 330); + FlatWorldTestBuilder.ClearBox(world, 318, 79, 318, 330, 90, 330); + FlatWorldTestBuilder.SetSolid(world, 320, 79, 320); + FlatWorldTestBuilder.SetSolid(world, 321, 79, 321); + FlatWorldTestBuilder.SetSolid(world, 322, 79, 322); + FlatWorldTestBuilder.SetSolid(world, 323, 79, 323); + FlatWorldTestBuilder.SetSolid(world, 324, 79, 324); + FlatWorldTestBuilder.SetSolid(world, 325, 79, 325); + FlatWorldTestBuilder.SetSolid(world, 326, 79, 326); + FlatWorldTestBuilder.SetSolid(world, 327, 79, 327); + return world; + } + + private static World BuildSameMoveAlignedParkourChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 378, max: 390); + FlatWorldTestBuilder.ClearBox(world, 378, 79, 378, 390, 90, 382); + FlatWorldTestBuilder.SetSolid(world, 380, 79, 380); + FlatWorldTestBuilder.SetSolid(world, 382, 79, 380); + FlatWorldTestBuilder.SetSolid(world, 384, 79, 380); + FlatWorldTestBuilder.SetSolid(world, 386, 79, 380); + FlatWorldTestBuilder.SetSolid(world, 388, 79, 380); + return world; + } + + private static World BuildMixedTraverseTurnParkourTurnTraverse() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 398, max: 410); + FlatWorldTestBuilder.ClearBox(world, 398, 79, 398, 410, 90, 406); + FlatWorldTestBuilder.SetSolid(world, 400, 79, 400); + FlatWorldTestBuilder.SetSolid(world, 401, 79, 400); + FlatWorldTestBuilder.SetSolid(world, 402, 79, 400); + FlatWorldTestBuilder.SetSolid(world, 402, 79, 401); + FlatWorldTestBuilder.SetSolid(world, 402, 79, 402); + FlatWorldTestBuilder.SetSolid(world, 404, 79, 402); + FlatWorldTestBuilder.SetSolid(world, 405, 79, 402); + FlatWorldTestBuilder.SetSolid(world, 406, 79, 402); + FlatWorldTestBuilder.SetSolid(world, 406, 79, 403); + FlatWorldTestBuilder.SetSolid(world, 406, 79, 404); + FlatWorldTestBuilder.SetSolid(world, 407, 79, 404); + FlatWorldTestBuilder.SetSolid(world, 408, 79, 404); + return world; + } + + private static World BuildMixedDiagonalAscendTraverseDescend() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 418, max: 430); + FlatWorldTestBuilder.ClearBox(world, 418, 79, 418, 430, 92, 424); + FlatWorldTestBuilder.SetSolid(world, 420, 79, 420); + FlatWorldTestBuilder.SetSolid(world, 421, 79, 421); + FlatWorldTestBuilder.SetSolid(world, 422, 79, 422); + FlatWorldTestBuilder.SetSolid(world, 423, 80, 422); + FlatWorldTestBuilder.SetSolid(world, 424, 81, 422); + FlatWorldTestBuilder.SetSolid(world, 425, 81, 422); + FlatWorldTestBuilder.SetSolid(world, 426, 81, 422); + FlatWorldTestBuilder.SetSolid(world, 427, 80, 422); + FlatWorldTestBuilder.SetSolid(world, 428, 79, 422); + return world; + } + + private static World BuildMixedTraverseAscendParkourDescend() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 438, max: 450); + FlatWorldTestBuilder.ClearBox(world, 438, 79, 438, 450, 92, 442); + FlatWorldTestBuilder.SetSolid(world, 440, 79, 440); + FlatWorldTestBuilder.SetSolid(world, 441, 79, 440); + FlatWorldTestBuilder.SetSolid(world, 442, 80, 440); + FlatWorldTestBuilder.SetSolid(world, 443, 81, 440); + FlatWorldTestBuilder.SetSolid(world, 444, 81, 440); + FlatWorldTestBuilder.SetSolid(world, 446, 81, 440); + FlatWorldTestBuilder.SetSolid(world, 447, 80, 440); + FlatWorldTestBuilder.SetSolid(world, 448, 79, 440); + return world; + } + + private static World BuildTurnDensityAlternatingTraverseDiagonalChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 458, max: 468); + FlatWorldTestBuilder.ClearBox(world, 458, 79, 458, 468, 90, 468); + FlatWorldTestBuilder.SetSolid(world, 460, 79, 460); + FlatWorldTestBuilder.SetSolid(world, 461, 79, 460); + FlatWorldTestBuilder.SetSolid(world, 461, 79, 461); + FlatWorldTestBuilder.SetSolid(world, 462, 79, 462); + FlatWorldTestBuilder.SetSolid(world, 463, 79, 462); + FlatWorldTestBuilder.SetSolid(world, 463, 79, 463); + FlatWorldTestBuilder.SetSolid(world, 464, 79, 464); + FlatWorldTestBuilder.SetSolid(world, 465, 79, 464); + FlatWorldTestBuilder.SetSolid(world, 465, 79, 465); + FlatWorldTestBuilder.SetSolid(world, 466, 79, 466); + return world; + } + + private static World BuildSpeedCarryRepeatedTraverseAscend() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 478, max: 490); + FlatWorldTestBuilder.ClearBox(world, 478, 79, 478, 490, 94, 482); + FlatWorldTestBuilder.SetSolid(world, 480, 79, 480); + FlatWorldTestBuilder.SetSolid(world, 481, 79, 480); + FlatWorldTestBuilder.SetSolid(world, 482, 80, 480); + FlatWorldTestBuilder.SetSolid(world, 483, 80, 480); + FlatWorldTestBuilder.SetSolid(world, 484, 81, 480); + FlatWorldTestBuilder.SetSolid(world, 485, 81, 480); + FlatWorldTestBuilder.SetSolid(world, 486, 82, 480); + FlatWorldTestBuilder.SetSolid(world, 487, 82, 480); + FlatWorldTestBuilder.SetSolid(world, 488, 83, 480); + return world; + } + + private static World BuildSpeedCarryRepeatedTraverseDescend() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 498, max: 510); + FlatWorldTestBuilder.ClearBox(world, 498, 79, 498, 510, 94, 502); + FlatWorldTestBuilder.SetSolid(world, 500, 82, 500); + FlatWorldTestBuilder.SetSolid(world, 501, 82, 500); + FlatWorldTestBuilder.SetSolid(world, 502, 81, 500); + FlatWorldTestBuilder.SetSolid(world, 503, 81, 500); + FlatWorldTestBuilder.SetSolid(world, 504, 80, 500); + FlatWorldTestBuilder.SetSolid(world, 505, 80, 500); + FlatWorldTestBuilder.SetSolid(world, 506, 79, 500); + FlatWorldTestBuilder.SetSolid(world, 507, 79, 500); + return world; + } + + private static World BuildSpeedCarryRepeatedTraverseParkour() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 518, max: 532); + FlatWorldTestBuilder.ClearBox(world, 518, 79, 518, 532, 90, 522); + FlatWorldTestBuilder.SetSolid(world, 520, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 521, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 523, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 524, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 526, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 527, 79, 520); + FlatWorldTestBuilder.SetSolid(world, 529, 79, 520); + return world; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs b/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs new file mode 100644 index 0000000000..6c84335cc9 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs @@ -0,0 +1,103 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public static class SidewallParkourScenarioBuilder +{ + private const int SegmentCount = 3; + private const int BaseX = 100; + private const int BaseY = 80; + private const int BaseZ = 100; + private const int FloorY = BaseY - 1; + + public static IEnumerable AcceptedCases() + { + yield return ["sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0]; + yield return ["sidewall-ascend-gap3-dy+1-wo0", 3, 1, 0]; + yield return ["sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1]; + yield return ["sidewall-descend-gap2-dy-2-wo0", 2, -2, 0]; + yield return ["sidewall-descend-gap3-dy-2-wo0", 3, -2, 0]; + yield return ["sidewall-descend-gap4-dy-2-wo0", 4, -2, 0]; + yield return ["sidewall-descend-gap5-dy-2-wo0", 5, -2, 0]; + yield return ["sidewall-descend-gap3-dy-2-wo1", 3, -2, 1]; + yield return ["sidewall-descend-gap4-dy-2-wo1", 4, -2, 1]; + yield return ["sidewall-descend-gap5-dy-2-wo1", 5, -2, 1]; + yield return ["sidewall-descend-gap2-dy-1-wo0", 2, -1, 0]; + yield return ["sidewall-descend-gap3-dy-1-wo0", 3, -1, 0]; + yield return ["sidewall-descend-gap4-dy-1-wo0", 4, -1, 0]; + yield return ["sidewall-descend-gap5-dy-1-wo0", 5, -1, 0]; + yield return ["sidewall-descend-gap3-dy-1-wo1", 3, -1, 1]; + yield return ["sidewall-descend-gap4-dy-1-wo1", 4, -1, 1]; + yield return ["sidewall-descend-gap5-dy-1-wo1", 5, -1, 1]; + yield return ["sidewall-flat-gap2-wo0", 2, 0, 0]; + yield return ["sidewall-flat-gap3-wo0", 3, 0, 0]; + yield return ["sidewall-flat-gap4-wo0", 4, 0, 0]; + yield return ["sidewall-flat-gap3-wo1", 3, 0, 1]; + yield return ["sidewall-flat-gap4-wo1", 4, 0, 1]; + } + + public static IEnumerable RejectedCases() + { + yield return ["sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0]; + yield return ["sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1]; + yield return ["sidewall-descend-gap6-dy-2-wo0", 6, -2, 0]; + yield return ["sidewall-descend-gap6-dy-2-wo1", 6, -2, 1]; + yield return ["sidewall-descend-gap6-dy-1-wo0", 6, -1, 0]; + yield return ["sidewall-descend-gap6-dy-1-wo1", 6, -1, 1]; + yield return ["sidewall-flat-gap5-wo0", 5, 0, 0]; + yield return ["sidewall-flat-gap5-wo1", 5, 0, 1]; + } + + internal static PathingExecutionScenario Create(string scenarioId, int gap, int deltaY, int wallOffset, int maxExecutionTicks = 700) + { + return new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = () => BuildWorld(gap, deltaY, wallOffset), + Start = new Location(BaseX + 0.5, BaseY, BaseZ + 0.5), + Goal = new GoalBlock(BaseX - SegmentCount, FloorY + (deltaY * SegmentCount) + 1, BaseZ + (gap * SegmentCount)), + StartYaw = 0f, + MaxExecutionTicks = maxExecutionTicks, + }; + } + + internal static World BuildWorld(int gap, int deltaY, int wallOffset) + { + int maxZ = BaseZ + (gap * SegmentCount); + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: 80, max: maxZ + 8); + FlatWorldTestBuilder.ClearBox(world, 90, 70, 90, 110, 96, maxZ + 8); + + int curX = BaseX; + int curY = FloorY; + int curZ = BaseZ; + + FlatWorldTestBuilder.FillSolid(world, curX, curY, curZ - 2, curX, curY, curZ); + + for (int segment = 0; segment < SegmentCount; segment++) + { + int landY = curY + deltaY; + int wallX = curX - 1; + + FlatWorldTestBuilder.FillSolid( + world, + wallX, + Math.Min(curY, landY) - 1, + curZ, + wallX, + Math.Max(curY, landY) + 7, + curZ + wallOffset); + + int landX = curX - 1; + int landZ = curZ + gap; + + FlatWorldTestBuilder.SetSolid(world, landX, landY, landZ); + + curX = landX; + curY = landY; + curZ = landZ; + } + + return world; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs b/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs new file mode 100644 index 0000000000..79b2b8b969 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs @@ -0,0 +1,177 @@ +using MinecraftClient.Mapping; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class SidewallParkourScenarioBuilderTests +{ + [Fact] + public void AcceptedCases_MatchLiveSidewallMatrix() + { + (string Id, int Gap, int DeltaY, int WallOffset)[] cases = SidewallParkourScenarioBuilder.AcceptedCases() + .Select(static c => AssertCase(c)) + .ToArray(); + + Assert.Equal( + [ + ("sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0), + ("sidewall-ascend-gap3-dy+1-wo0", 3, 1, 0), + ("sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1), + ("sidewall-descend-gap2-dy-2-wo0", 2, -2, 0), + ("sidewall-descend-gap3-dy-2-wo0", 3, -2, 0), + ("sidewall-descend-gap4-dy-2-wo0", 4, -2, 0), + ("sidewall-descend-gap5-dy-2-wo0", 5, -2, 0), + ("sidewall-descend-gap3-dy-2-wo1", 3, -2, 1), + ("sidewall-descend-gap4-dy-2-wo1", 4, -2, 1), + ("sidewall-descend-gap5-dy-2-wo1", 5, -2, 1), + ("sidewall-descend-gap2-dy-1-wo0", 2, -1, 0), + ("sidewall-descend-gap3-dy-1-wo0", 3, -1, 0), + ("sidewall-descend-gap4-dy-1-wo0", 4, -1, 0), + ("sidewall-descend-gap5-dy-1-wo0", 5, -1, 0), + ("sidewall-descend-gap3-dy-1-wo1", 3, -1, 1), + ("sidewall-descend-gap4-dy-1-wo1", 4, -1, 1), + ("sidewall-descend-gap5-dy-1-wo1", 5, -1, 1), + ("sidewall-flat-gap2-wo0", 2, 0, 0), + ("sidewall-flat-gap3-wo0", 3, 0, 0), + ("sidewall-flat-gap4-wo0", 4, 0, 0), + ("sidewall-flat-gap3-wo1", 3, 0, 1), + ("sidewall-flat-gap4-wo1", 4, 0, 1), + ], + cases); + } + + [Fact] + public void RejectedCases_MatchLiveSidewallMatrix() + { + (string Id, int Gap, int DeltaY, int WallOffset)[] cases = SidewallParkourScenarioBuilder.RejectedCases() + .Select(static c => AssertCase(c)) + .ToArray(); + + Assert.Equal( + [ + ("sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0), + ("sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1), + ("sidewall-descend-gap6-dy-2-wo0", 6, -2, 0), + ("sidewall-descend-gap6-dy-2-wo1", 6, -2, 1), + ("sidewall-descend-gap6-dy-1-wo0", 6, -1, 0), + ("sidewall-descend-gap6-dy-1-wo1", 6, -1, 1), + ("sidewall-flat-gap5-wo0", 5, 0, 0), + ("sidewall-flat-gap5-wo1", 5, 0, 1), + ], + cases); + } + + [Fact] + public void BuildWorld_FlatGap2Wo0_MatchesLiveRouteGeometry() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 98)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 99)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 102)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(100, 79, 101)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(98, 78, 102)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(97, 79, 106)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(98, 79, 103)).Type); + } + + [Fact] + public void BuildWorld_FlatGap3Wo1_ExtendsWallByTwoBlocksAlongRunwaySide() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 3, deltaY: 0, wallOffset: 1); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 101)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(99, 78, 102)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 103)).Type); + } + + [Fact] + public void BuildWorld_AscendGap3Wo1_RaisesWallsAndLandingsAcrossSegments() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 3, deltaY: 1, wallOffset: 1); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 87, 101)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(99, 88, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 80, 103)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(98, 79, 103)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(98, 78, 103)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(97, 82, 109)).Type); + } + + [Fact] + public void BuildWorld_DescendGap5Wo1_LowersWallsAndLandingsAcrossSegments() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 5, deltaY: -2, wallOffset: 1); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 76, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 86, 101)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(99, 75, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 77, 105)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(98, 74, 105)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(98, 73, 105)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(97, 73, 115)).Type); + } + + [Fact] + public void Create_FlatGap2Wo0_UsesSameStartAndGoalAsLiveHarness() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + "sidewall-flat-gap2-wo0", + gap: 2, + deltaY: 0, + wallOffset: 0); + + Assert.Equal(new Location(100.5, 80, 100.5), scenario.Start); + Assert.Equal(97, scenario.Goal.X); + Assert.Equal(80, scenario.Goal.Y); + Assert.Equal(106, scenario.Goal.Z); + Assert.Equal(0f, scenario.StartYaw); + } + + [Fact] + public void Create_AscendGap3Wo1_ComputesGoalForLaterRaisedEndpoint() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + "sidewall-ascend-gap3-dy+1-wo1", + gap: 3, + deltaY: 1, + wallOffset: 1); + + Assert.Equal(new Location(100.5, 80, 100.5), scenario.Start); + Assert.Equal(97, scenario.Goal.X); + Assert.Equal(83, scenario.Goal.Y); + Assert.Equal(109, scenario.Goal.Z); + Assert.Equal(0f, scenario.StartYaw); + } + + [Fact] + public void Create_DescendGap5Wo1_ComputesGoalForLowerEndpoint() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + "sidewall-descend-gap5-dy-2-wo1", + gap: 5, + deltaY: -2, + wallOffset: 1); + + Assert.Equal(new Location(100.5, 80, 100.5), scenario.Start); + Assert.Equal(97, scenario.Goal.X); + Assert.Equal(74, scenario.Goal.Y); + Assert.Equal(115, scenario.Goal.Z); + Assert.Equal(0f, scenario.StartYaw); + } + + private static (string Id, int Gap, int DeltaY, int WallOffset) AssertCase(object[] values) + { + Assert.Equal(4, values.Length); + + return ( + Assert.IsType(values[0]), + Assert.IsType(values[1]), + Assert.IsType(values[2]), + Assert.IsType(values[3])); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs b/MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs new file mode 100644 index 0000000000..2a7053965c --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs @@ -0,0 +1,992 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class SprintJumpTemplateScenarioTests +{ + [Fact] + public void SprintJumpTemplate_TwoBlockGap_FinalStop_Completes() + { + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void SprintJumpTemplate_TwoBlockGap_FinalStop_CompletesFromOppositeYaw() + { + 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); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void SprintJumpTemplate_TwoBlockGap_FinalStop_CompletesFromOppositeYawWithinTwentyTicks() + { + 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 = TemplateState.InProgress; + int elapsedTicks = 0; + Location finalPos = segment.Start; + var trace = new List(); + for (; elapsedTicks < 80; elapsedTicks++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + if (elapsedTicks < 20 || state != TemplateState.InProgress) + { + trace.Add( + $"tick={elapsedTicks} state={state} pos={pos} yaw={physics.Yaw:F1} vel={physics.DeltaMovement} " + + $"onGround={physics.OnGround} input(F={input.Forward},J={input.Jump},S={input.Sprint})"); + } + + if (state != TemplateState.InProgress) + { + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + } + + Assert.Equal(TemplateState.Complete, state); + Assert.True(elapsedTicks <= 20, $"elapsedTicks={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End), $"elapsedTicks={elapsedTicks} finalPos={finalPos} vel={physics.DeltaMovement}\n{string.Join('\n', trace)}"); + } + + [Fact] + public void SprintJumpTemplate_ThreeBlockGap_FinalStop_Completes() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 0, 79, 0, 5, 82, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 3, 79, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(3.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void SprintJumpTemplate_TwoBlockGap_LandingRecovery_CompletesOnTurnEntrySupportStrip() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 0, 79, 0, 4, 82, 2); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 80, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 81, 1); + + 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.LandingRecovery + }; + var next = new PathSegment + { + Start = new Location(2.5, 80, 0.5), + End = new Location(2.5, 80, 1.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideSupportStrip(finalPos, segment.End, next.End), + $"finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void SprintJumpTemplate_LandingRecoveryIntoTurn_CompletesWithLowResidualSpeed() + { + 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, 123, 79, 110); + FlatWorldTestBuilder.SetSolid(world, 123, 79, 111); + + var segment = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.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 = new Location(123.5, 80, 110.5), + End = new Location(123.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.03, true, true, false, false, 12) + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 160, out Location finalPos); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideSupportStrip(finalPos, segment.End, next.End), + $"finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.InRange(horizontalSpeed, 0.0, 0.04); + } + + [Fact] + public void SprintJumpTemplate_PrepareJumpIntoSecondParkour_CompletesWithoutSettling() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 0, 79, 0, 6, 82, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 4, 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.PrepareJump, + ExitHints = new PathTransitionHints(1, 0, 0.10, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(2.5, 80, 0.5), + End = new Location(4.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, segment.End), $"finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(horizontalSpeed > 0.02, $"finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void SprintJumpTemplate_FinalStop_CompletesAfterPrepareJumpCarry() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 0, 79, 0, 6, 82, 1); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 4, 79, 0); + + var first = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(2.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.PrepareJump, + ExitHints = new PathTransitionHints(1, 0, 0.10, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + var second = new PathSegment + { + Start = new Location(2.5, 80, 0.5), + End = new Location(4.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + }; + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(first.Start, yaw: 270f); + + TemplateState firstState = TemplateSimulationRunner.Run(new SprintJumpTemplate(first, second), physics, world, maxTicks: 140, out Location handoffPos); + TemplateState secondState = TemplateSimulationRunner.Run(new SprintJumpTemplate(second, null), physics, world, maxTicks: 140, out Location finalPos); + + Assert.Equal(TemplateState.Complete, firstState); + Assert.True( + secondState == TemplateState.Complete, + $"secondState={secondState} handoffPos={handoffPos} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, second.End), $"handoffPos={handoffPos} finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void SprintJumpTemplate_LinearFlatGap4_PrepareJump_CompletesFromTraverseCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[3].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearFlatGap1_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap1", gap: 1, deltaY: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 3); + + TemplateState state = RunSegment(segments, index: 4, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[4].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearFlatGap1_PrepareJump_HandoffStaysInsideTargetBlock() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap1", gap: 1, deltaY: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location currentPos, out string trace); + + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(currentPos, segments[4].Start), + $"state={state} currentPos={currentPos} vel={physics.DeltaMovement} segment={segments[3]} next={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearAscendGap2DyPlus1_PrepareJump_CompletesFromTraverseCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-ascend-gap2-dy+1", gap: 2, deltaY: 1); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + List segments = PathSegmentBuilder.FromPath(result.PlanResult.Path); + PathSegmentRun? segmentRun = FindSegmentRun(result, segmentIndex: 3); + + Assert.True( + result.Completed && result.ReplanCount == 0, + $"completed={result.Completed} replans={result.ReplanCount} finalPos={result.FinalPosition}\n" + + $"info={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + Assert.NotNull(segmentRun); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(segmentRun.Position, segments[3].End), + $"segmentPos={segmentRun.Position} target={segments[3].End} finalPos={result.FinalPosition}\n" + + $"info={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + } + + [Fact] + public void SprintJumpTemplate_LinearAscendGap2DyPlus1_SecondPrepareJump_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-ascend-gap2-dy+1", gap: 2, deltaY: 1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 3); + + TemplateState state = RunSegment(segments, index: 4, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[4].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearAscendGap1DyPlus1_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-ascend-gap1-dy+1", gap: 1, deltaY: 1); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + List segments = PathSegmentBuilder.FromPath(result.PlanResult.Path); + PathSegmentRun? segmentRun = FindSegmentRun(result, segmentIndex: 5); + + Assert.True( + result.Completed && result.ReplanCount == 0, + $"completed={result.Completed} replans={result.ReplanCount} finalPos={result.FinalPosition}\n" + + $"info={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + Assert.NotNull(segmentRun); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(segmentRun.Position, segments[5].End), + $"segmentPos={segmentRun.Position} target={segments[5].End} finalPos={result.FinalPosition}\n" + + $"info={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap2DyMinus2_PrepareJump_CompletesFromTraverseCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap2-dy-2", gap: 2, deltaY: -2); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[3].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap2DyMinus2_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap2-dy-2", gap: 2, deltaY: -2); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 4); + + TemplateState state = RunSegment(segments, index: 5, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[5].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap2DyMinus1_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap2-dy-1", gap: 2, deltaY: -1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 4); + + TemplateState state = RunSegment(segments, index: 5, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[5].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap2DyMinus1_SecondPrepareJump_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap2-dy-1", gap: 2, deltaY: -1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 3); + + TemplateState state = RunSegment(segments, index: 4, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[4].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap3DyMinus2_PrepareJump_CompletesFromTraverseCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap3-dy-2", gap: 3, deltaY: -2); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[3].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap3DyMinus2_PrepareJump_HandoffStaysInsideTargetBlock() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap3-dy-2", gap: 3, deltaY: -2); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location currentPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} currentPos={currentPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(currentPos, segments[4].Start), + $"state={state} currentPos={currentPos} vel={physics.DeltaMovement} segment={segments[3]} next={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap3DyMinus1_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap3-dy-1", gap: 3, deltaY: -1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 4); + + TemplateState state = RunSegment(segments, index: 5, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[5].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap4DyMinus1_PrepareJump_CompletesFromTraverseCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap4-dy-1", gap: 4, deltaY: -1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 2); + + TemplateState state = RunSegment(segments, index: 3, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[3].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[3]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearDescendGap4DyMinus1_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-descend-gap4-dy-1", gap: 4, deltaY: -1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 4); + + TemplateState state = RunSegment(segments, index: 5, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[5].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearFlatGap2_SecondPrepareJump_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap2", gap: 2, deltaY: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 3); + + TemplateState state = RunSegment(segments, index: 4, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[4].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[4]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_LinearFlatGap2_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap2", gap: 2, deltaY: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedLinearScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 4); + + TemplateState state = RunSegment(segments, index: 5, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[5].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[5]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_DiagonalLandingRecovery_HandsOffToTurnTraverse() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: -1, max: 8); + FlatWorldTestBuilder.ClearBox(world, -1, 79, -1, 8, 82, 4); + FlatWorldTestBuilder.SetSolid(world, 0, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 1); + FlatWorldTestBuilder.SetSolid(world, 3, 79, 1); + FlatWorldTestBuilder.SetSolid(world, 4, 79, 2); + + var parkour = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(2.5, 80, 1.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.LandingRecovery, + ExitHints = new PathTransitionHints(1, 0, 0.0, 0.05, true, true, false, true, 12) + }; + var traverse = new PathSegment + { + Start = new Location(2.5, 80, 1.5), + End = new Location(3.5, 80, 1.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.Turn, + ExitHints = new PathTransitionHints(1, 1, 0.0, 0.05, true, true, false, true, 12) + }; + var next = new PathSegment + { + Start = new Location(3.5, 80, 1.5), + End = new Location(4.5, 80, 2.5), + MoveType = MoveType.Diagonal, + ExitTransition = PathTransitionType.FinalStop + }; + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(parkour.Start, yaw: 315f); + + TemplateState parkourState = TemplateSimulationRunner.Run(new SprintJumpTemplate(parkour, traverse), physics, world, maxTicks: 160, out Location handoffPos); + TemplateState traverseState = TemplateSimulationRunner.Run(new WalkTemplate(traverse, next), physics, world, maxTicks: 160, out Location finalPos); + + Assert.Equal(TemplateState.Complete, parkourState); + Assert.True( + traverseState == TemplateState.Complete, + $"traverseState={traverseState} handoffPos={handoffPos} finalPos={finalPos} vel={physics.DeltaMovement}"); + Assert.True( + TemplateFootingHelper.IsCenterInsideTargetBlock(finalPos, traverse.End), + $"handoffPos={handoffPos} finalPos={finalPos} vel={physics.DeltaMovement}"); + } + + [Fact] + public void SprintJumpTemplate_ThreeBlockGap_WithIsolatedTakeoffBlock_JumpsImmediately() + { + 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, 123, 79, 110); + + var segment = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.5, 80, 110.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop, + }; + + var template = new SprintJumpTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + var input = new MovementInput(); + + TemplateState state = template.Tick(segment.Start, physics, input, world); + + Assert.Equal(TemplateState.InProgress, state); + Assert.True(input.Forward); + Assert.True(input.Sprint); + Assert.True(input.Jump); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap2_FinalStop_CompletesInsideLandingBlock() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0); + var segment = new PathSegment + { + Start = new Location(100.5, 80, 100.5), + End = new Location(99.5, 80, 102.5), + MoveType = MoveType.Parkour, + ParkourProfile = ParkourProfile.Sidewall, + ExitTransition = PathTransitionType.FinalStop + }; + + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f); + PathSegment[] segments = [segment]; + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True( + state == TemplateState.Complete, + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segment}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segment}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap2_SecondPrepareJump_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap2-wo0", gap: 2, deltaY: 0, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 0); + + TemplateState state = RunSegment(segments, index: 1, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[1].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap3Wo1_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap3-wo1", gap: 3, deltaY: 0, wallOffset: 1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap4Wo0_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap4-wo0", gap: 4, deltaY: 0, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap3Wo1_SecondPrepareJump_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap3-wo1", gap: 3, deltaY: 0, wallOffset: 1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 0); + + TemplateState state = RunSegment(segments, index: 1, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[1].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallAscendGap3Wo1_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-ascend-gap3-dy+1-wo1", gap: 3, deltaY: 1, wallOffset: 1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap2DyMinus1Wo0_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-1-wo0", gap: 2, deltaY: -1, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap2DyMinus2Wo0_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-2-wo0", gap: 2, deltaY: -2, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap2DyMinus1Wo0_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-1-wo0", gap: 2, deltaY: -1, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap2DyMinus2Wo0_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-2-wo0", gap: 2, deltaY: -2, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap5DyMinus1Wo0_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-1-wo0", gap: 5, deltaY: -1, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap5DyMinus1Wo1_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-1-wo1", gap: 5, deltaY: -1, wallOffset: 1); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap5DyMinus2Wo0_FirstPrepareJump_CompletesFromStart() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-2-wo0", gap: 5, deltaY: -2, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap5DyMinus2Wo0_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-2-wo0", gap: 5, deltaY: -2, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap3DyMinus1Wo0_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap3-dy-1-wo0", gap: 3, deltaY: -1, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallFlatGap2_FinalStop_CompletesFromChainCarry() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap2-wo0", gap: 2, deltaY: 0, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + [Fact] + public void SprintJumpTemplate_SidewallDescendGap4DyMinus1Wo0_FinalStop_CompletesInsideLandingBlock() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap4-dy-1-wo0", gap: 4, deltaY: -1, wallOffset: 0); + (World world, List segments, PlayerPhysics physics) = BuildPlannedScenario(scenario); + + RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1); + + TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace); + + Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + Assert.True( + TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End), + $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}"); + } + + private static (World World, List Segments, PlayerPhysics Physics) BuildPlannedLinearScenario(PathingExecutionScenario scenario) + { + return BuildPlannedScenario(scenario); + } + + private static (World World, List Segments, PlayerPhysics Physics) BuildPlannedScenario(PathingExecutionScenario scenario) + { + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + Assert.Equal(PathStatus.Success, planResult.Status); + + return ( + scenario.BuildWorld(), + PathSegmentBuilder.FromPath(planResult.Path), + TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw)); + } + + private static void RunSegmentsThrough(IReadOnlyList segments, World world, PlayerPhysics physics, int lastCompletedIndex) + { + for (int index = 0; index <= lastCompletedIndex; index++) + { + TemplateState state = RunSegment(segments, index, physics, world, out Location finalPos, out string trace); + Assert.True( + state == TemplateState.Complete, + $"segmentIndex={index} state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[index]}\n{trace}"); + } + } + + private static TemplateState RunSegment( + IReadOnlyList segments, + int index, + PlayerPhysics physics, + World world, + out Location finalPos, + out string trace) + { + PathSegment segment = segments[index]; + PathSegment? next = index + 1 < segments.Count ? segments[index + 1] : null; + IActionTemplate template = ActionTemplateFactory.Create(segment, next); + var input = new MovementInput(); + var tail = new Queue(); + TemplateState state = TemplateState.InProgress; + + for (int tick = 0; tick < 160; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + tail.Enqueue( + $"tick={tick} state={state} pos={pos} vel={physics.DeltaMovement} yaw={physics.Yaw:F1} onGround={physics.OnGround} " + + $"input(F={input.Forward},B={input.Back},L={input.Left},R={input.Right},J={input.Jump},S={input.Sprint})"); + if (tail.Count > 40) + tail.Dequeue(); + + if (state != TemplateState.InProgress) + { + if (state == TemplateState.Complete && next is not null) + { + physics.ApplyInput(input); + physics.Tick(world); + } + break; + } + + physics.ApplyInput(input); + physics.Tick(world); + } + + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + trace = string.Join('\n', tail); + return state; + } + + private static PathSegmentRun? FindSegmentRun(PathingScenarioResult result, int segmentIndex) + { + foreach (PathSegmentRun run in result.SegmentRuns) + { + if (run.SegmentIndex == segmentIndex) + return run; + } + + return null; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs b/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs new file mode 100644 index 0000000000..42e2873e21 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs @@ -0,0 +1,89 @@ +using System.Text; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Tests.Pathing.Execution.Contracts; +using Xunit; +using Xunit.Sdk; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingContractAssert +{ + internal static void PlannerMatches(PathingPlannerContract contract, IReadOnlyList segments, PathResult result) + { + if (result.Status != contract.ExpectedStatus) + throw new XunitException($"planner status mismatch: expected {contract.ExpectedStatus}, got {result.Status}"); + + if (segments.Count != contract.Segments.Count) + throw new XunitException($"segment count mismatch: expected {contract.Segments.Count}, got {segments.Count}"); + + for (int i = 0; i < segments.Count; i++) + { + PathSegment actual = segments[i]; + PathingPlannerSegmentContract expected = contract.Segments[i]; + + Assert.Equal(expected.MoveType, actual.MoveType); + Assert.Equal(expected.StartBlock, ToBlock(actual.Start)); + Assert.Equal(expected.EndBlock, ToBlock(actual.End)); + } + } + + internal static void TimingMatches(PathingTimingBudget budget, PathingScenarioResult result) + { + if (!result.Completed) + throw new XunitException($"navigation did not complete\n{Format(result, budget)}"); + + if (result.ReplanCount != 0) + throw new XunitException($"expected 0 replans, saw {result.ReplanCount}\n{Format(result, budget)}"); + + if (result.TotalTicks > budget.MaxTotalTicks) + throw new XunitException($"route exceeded budget: actual={result.TotalTicks} max={budget.MaxTotalTicks}\n{Format(result, budget)}"); + + if (result.SegmentRuns.Count != budget.Segments.Count) + throw new XunitException($"segment timing count mismatch: actual={result.SegmentRuns.Count} expected={budget.Segments.Count}"); + + for (int i = 0; i < budget.Segments.Count; i++) + { + if (result.SegmentRuns[i].ElapsedTicks > budget.Segments[i].MaxTicks) + throw new XunitException($"segment {i} exceeded budget\n{Format(result, budget)}"); + } + } + + private static string Format(PathingScenarioResult result, PathingTimingBudget budget) + { + var sb = new StringBuilder(); + sb.AppendLine($"completed={result.Completed} replans={result.ReplanCount}"); + sb.AppendLine($"route actual={result.TotalTicks} expected={budget.ExpectedTotalTicks} max={budget.MaxTotalTicks}"); + for (int i = 0; i < result.SegmentRuns.Count; i++) + { + PathSegmentRun actual = result.SegmentRuns[i]; + PathingSegmentTimingBudget expected = budget.Segments[i]; + 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 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)); +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs b/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs new file mode 100644 index 0000000000..82deb0da3e --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs @@ -0,0 +1,62 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingContractBootstrapWriter +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = true + }; + + static PathingContractBootstrapWriter() + { + JsonOptions.Converters.Add(new JsonStringEnumConverter()); + } + + internal static string WritePlannerFragment(string scenarioId, PathResult planResult) + { + IReadOnlyList segments = PathSegmentBuilder.FromPath(planResult.Path); + + return JsonSerializer.Serialize(new + { + scenarioId, + expectedStatus = planResult.Status, + segments = segments.Select(segment => new + { + moveType = segment.MoveType, + startBlock = ToBlockObject(segment.Start), + endBlock = ToBlockObject(segment.End) + }) + }, JsonOptions); + } + + internal static string WriteTimingFragment(string scenarioId, PathingScenarioResult result) + { + return JsonSerializer.Serialize(new + { + scenarioId, + expectedTotalTicks = result.TotalTicks, + maxTotalTicks = SeedMaxTicks(result.TotalTicks), + segments = result.SegmentRuns.Select(run => new + { + moveType = run.MoveType, + expectedTicks = run.ElapsedTicks, + maxTicks = SeedMaxTicks(run.ElapsedTicks) + }) + }, JsonOptions); + } + + private static object ToBlockObject(MinecraftClient.Mapping.Location location) => new + { + x = (int)Math.Floor(location.X), + y = (int)Math.Floor(location.Y), + z = (int)Math.Floor(location.Z) + }; + + private static int SeedMaxTicks(int expectedTicks) => + expectedTicks + Math.Max(2, (int)Math.Ceiling(expectedTicks * 0.20)); +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs b/MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs new file mode 100644 index 0000000000..24c830a77f --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs @@ -0,0 +1,76 @@ +using System.Threading; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathingScenarioResult( + bool Completed, + int ReplanCount, + int TotalTicks, + IReadOnlyList SegmentRuns, + IReadOnlyList DebugLogs, + IReadOnlyList InfoLogs, + Location FinalPosition, + PathResult PlanResult); + +internal static class PathingScenarioRunner +{ + internal static PathResult PlanOnly(PathingExecutionScenario scenario) + { + World world = scenario.BuildWorld(); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + + return finder.Calculate( + ctx, + (int)Math.Floor(scenario.Start.X), + (int)Math.Floor(scenario.Start.Y), + (int)Math.Floor(scenario.Start.Z), + scenario.Goal, + CancellationToken.None, + timeoutMs: 3000); + } + + internal static PathingScenarioResult RunAccepted(PathingExecutionScenario scenario) + { + World world = scenario.BuildWorld(); + var debugLogs = new List(); + var infoLogs = new List(); + var observer = new RecordingPathExecutionObserver(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add, observer); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw); + var input = new MovementInput(); + + PathResult planResult = PlanOnly(scenario); + + manager.StartNavigation(scenario.Goal, planResult); + + for (int tick = 0; tick < scenario.MaxExecutionTicks && 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); + } + + Location finalPosition = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + bool completed = !manager.IsNavigating && manager.Goal is null && observer.TotalTicks > 0; + + return new PathingScenarioResult( + completed, + observer.ReplanCount, + observer.TotalTicks, + observer.SegmentRuns.AsReadOnly(), + debugLogs.AsReadOnly(), + infoLogs.AsReadOnly(), + finalPosition, + planResult); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs b/MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs new file mode 100644 index 0000000000..f862c351ec --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs @@ -0,0 +1,33 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Telemetry; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathSegmentRun(int SegmentIndex, MoveType MoveType, int ElapsedTicks, Location Position); + +internal sealed class RecordingPathExecutionObserver : IPathExecutionObserver +{ + internal List SegmentRuns { get; } = []; + internal int ReplanCount { get; private set; } + internal int TotalTicks { get; private set; } + + public void OnNavigationStarted(IReadOnlyList segments) { } + + public void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment) { } + + public void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => SegmentRuns.Add(new PathSegmentRun(segmentIndex, segment.MoveType, elapsedTicks, position)); + + public void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => SegmentRuns.Add(new PathSegmentRun(segmentIndex, segment.MoveType, elapsedTicks, position)); + + public void OnNavigationCompleted(int totalTicks) => TotalTicks = totalTicks; + + public void OnReplanStarted(int replanCount, Location position) => ReplanCount = replanCount; + + public void OnReplanSucceeded(int replanCount, IReadOnlyList segments) { } + + public void OnReplanFailed(int replanCount, Location position) { } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs b/MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs new file mode 100644 index 0000000000..4e7c443614 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs @@ -0,0 +1,79 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TemplateBrakingTests +{ + [Fact] + public void WalkTemplate_BackBrakes_WhenFinalStopIsTooClose() + { + 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, + PreserveSprint = false + }; + + var template = new WalkTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.38, 80.0, 0.5), + DeltaMovement = new Vec3d(0.156, 0.0, 0.0), + OnGround = true, + Yaw = 270f + }; + var input = new MovementInput(); + + TemplateState state = template.Tick(new Location(1.38, 80, 0.5), physics, input, world); + + Assert.Equal(TemplateState.InProgress, state); + Assert.False(input.Forward); + Assert.False(input.Sprint); + Assert.True(input.Back); + } + + [Fact] + public void WalkTemplate_KeepsForward_WhenTransitionContinuesStraight() + { + 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.ContinueStraight, + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(1.5, 80, 0.5), + End = new Location(2.5, 80, 0.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new WalkTemplate(current, next); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.10, 80.0, 0.5), + DeltaMovement = new Vec3d(0.140, 0.0, 0.0), + OnGround = true, + Yaw = 270f + }; + var input = new MovementInput(); + + TemplateState state = template.Tick(new Location(1.10, 80, 0.5), physics, input, world); + + Assert.Equal(TemplateState.InProgress, state); + Assert.True(input.Forward); + Assert.True(input.Sprint); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs b/MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs new file mode 100644 index 0000000000..1502350c4d --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs @@ -0,0 +1,98 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TemplateFootingTests +{ + [Fact] + public void IsFootprintInsideTargetBlock_ReturnsTrue_WhenPlayerIsNearEdgeButStillInside() + { + bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock( + new Location(10.69, 80.0, 4.50), + new Location(10.50, 80.0, 4.50)); + + Assert.True(inside); + } + + [Fact] + public void IsFootprintInsideTargetBlock_ReturnsFalse_WhenPlayerCrossesBlockEdge() + { + bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock( + new Location(10.81, 80.0, 4.50), + new Location(10.50, 80.0, 4.50)); + + Assert.False(inside); + } + + [Fact] + public void WillLeaveTargetBlockNextTick_ReturnsTrue_WhenVelocityWouldCarryPastEdge() + { + var physics = new PlayerPhysics + { + Position = new Vec3d(10.67, 80.0, 4.50), + DeltaMovement = new Vec3d(0.060, 0.0, 0.0), + OnGround = true + }; + + bool exitsNextTick = TemplateFootingHelper.WillLeaveTargetBlockNextTick( + new Location(10.67, 80.0, 4.50), + physics, + new Location(10.50, 80.0, 4.50)); + + Assert.True(exitsNextTick); + } + + [Fact] + public void IsCenterInsideTargetBlock_ReturnsTrue_WhenPlayerStopsNearEdge() + { + bool inside = TemplateFootingHelper.IsCenterInsideTargetBlock( + new Location(10.29, 80.0, 4.50), + new Location(10.50, 80.0, 4.50)); + + Assert.True(inside); + } + + [Fact] + public void IsFootprintInsideSupportStrip_ReturnsTrue_WhenPlayerStraddlesTurnEntryBlocks() + { + bool inside = TemplateFootingHelper.IsFootprintInsideSupportStrip( + new Location(123.46, 80.0, 110.77), + new Location(123.50, 80.0, 110.50), + new Location(123.50, 80.0, 111.50)); + + Assert.True(inside); + } + + [Fact] + public void WillLeaveSupportStripNextTick_ReturnsFalse_WhenLowSpeedStaysOnTurnEntryBlocks() + { + var physics = new PlayerPhysics + { + Position = new Vec3d(123.46, 80.0, 110.77), + DeltaMovement = new Vec3d(-0.0199, 0.0, 0.0040), + OnGround = true + }; + + bool exitsNextTick = TemplateFootingHelper.WillLeaveSupportStripNextTick( + new Location(123.46, 80.0, 110.77), + physics, + new Location(123.50, 80.0, 110.50), + new Location(123.50, 80.0, 111.50)); + + Assert.False(exitsNextTick); + } + + [Fact] + public void IsCenterInsideSupportStrip_ReturnsTrue_WhenLowSpeedTurnEntryStopsOnSeam() + { + bool inside = TemplateFootingHelper.IsCenterInsideSupportStrip( + new Location(123.46, 80.0, 110.77), + new Location(123.50, 80.0, 110.50), + new Location(123.50, 80.0, 111.50)); + + Assert.True(inside); + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs b/MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs new file mode 100644 index 0000000000..ca3781f07e --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs @@ -0,0 +1,75 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class TemplateSimulationRunner +{ + internal static PlayerPhysics CreateGroundedPhysics(Location start, float yaw, int initialMomentumTicks = 0) + { + var physics = new PlayerPhysics + { + Position = new Vec3d(start.X, start.Y, start.Z), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = yaw, + Pitch = 0f + }; + + if (initialMomentumTicks > 0) + ApplyInitialGroundMomentum(physics, initialMomentumTicks); + + return physics; + } + + internal static TemplateState Run(IActionTemplate template, PlayerPhysics physics, World world, int maxTicks, out Location finalPos) + { + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + + for (int tick = 0; tick < maxTicks; tick++) + { + 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); + } + + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + return state; + } + + private static void ApplyInitialGroundMomentum(PlayerPhysics physics, int ticks) + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var seeded = new PlayerPhysics + { + Position = new Vec3d(0.5, 80, 0.5), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = physics.MovementSpeed, + Yaw = physics.Yaw, + Pitch = physics.Pitch + }; + var input = new MovementInput + { + Forward = true, + Sprint = true + }; + + for (int tick = 0; tick < ticks; tick++) + { + seeded.ApplyInput(input); + seeded.Tick(world); + } + + physics.DeltaMovement = seeded.DeltaMovement; + physics.Sprinting = true; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs b/MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs new file mode 100644 index 0000000000..0a7b751588 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs @@ -0,0 +1,194 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TransitionBrakingPlannerTests +{ + [Fact] + public void Plan_ReturnsCarryMomentum_ForContinueStraight() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.156, 0.0, onGround: true); + 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.ContinueStraight, + ExitHints = new PathTransitionHints(1, 0, 0.0, double.PositiveInfinity, false, false, false, false, 8), + PreserveSprint = true + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.05, 80, 0.5), physics, world); + + Assert.True(decision.HoldForward); + Assert.True(decision.HoldSprint); + Assert.False(decision.HoldBack); + } + + [Fact] + public void Plan_Brakes_ForTurnEntryRequiringSlowSpeed() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.156, 0.0, onGround: true); + 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.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.035, true, true, false, true, 12), + PreserveSprint = false + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.38, 80, 0.5), physics, world); + + Assert.False(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.True(decision.HoldBack); + } + + [Fact] + public void Plan_Carries_ForPrepareJumpNeedingRunUpSpeed() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.0, 0.0, onGround: true); + 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.12, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.02, 80, 0.5), physics, world); + + Assert.True(decision.HoldForward); + Assert.True(decision.HoldSprint); + Assert.False(decision.HoldBack); + } + + [Fact] + public void Plan_BackBrakes_ForFinalStop_WhenRemainingRunwayIsTooShort() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.156, 0.0, onGround: true); + 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.FinalStop, + ExitHints = new PathTransitionHints(1, 0, 0.0, 0.03, true, true, false, false, 12), + PreserveSprint = false + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.38, 80, 0.5), physics, world); + + Assert.False(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.True(decision.HoldBack); + } + + [Fact] + public void Plan_NudgesForward_ForFinalStop_WhenAlreadySlowButStillShort() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.0, 0.0, onGround: true); + 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.FinalStop, + ExitHints = new PathTransitionHints(1, 0, 0.0, 0.03, true, true, false, false, 12), + PreserveSprint = false + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.41, 80, 0.5), physics, world); + + Assert.True(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.False(decision.HoldBack); + } + + [Fact] + public void ShouldReleaseForwardInAir_ReturnsTrue_ForParkourIntoTurn() + { + var physics = CreatePhysics(0.32, 0.0, onGround: false); + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.5, 80, 110.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.035, true, true, false, true, 12), + PreserveSprint = false + }; + var next = new PathSegment + { + Start = new Location(123.5, 80, 110.5), + End = new Location(123.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 108, max: 126); + + bool release = TransitionBrakingPlanner.ShouldReleaseForwardInAir(current, next, new Location(123.18, 80.92, 110.5), physics, world); + + Assert.True(release); + } + + [Fact] + public void Plan_BackBrakes_ForLandingRecovery_WhenNextSegmentTurns() + { + 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 physics = CreatePhysics(0.118, 0.018, onGround: true); + 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), + PreserveSprint = false + }; + var next = new PathSegment + { + Start = new Location(122.5, 80, 110.5), + End = new Location(122.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, next, new Location(122.56, 80, 110.68), physics, world); + + Assert.False(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.True(decision.HoldBack); + } + + private static PlayerPhysics CreatePhysics(double deltaX, double deltaZ, bool onGround) + { + return new PlayerPhysics + { + Position = new Vec3d(0.0, 80.0, 0.0), + DeltaMovement = new Vec3d(deltaX, 0.0, deltaZ), + OnGround = onGround, + MovementSpeed = 0.1f, + Yaw = 270f + }; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs b/MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs new file mode 100644 index 0000000000..800327a994 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs @@ -0,0 +1,179 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TransitionLookaheadEvaluatorTests +{ + [Fact] + public void ChooseGroundProfile_PicksBrake_WhenTurnEntryCapsResidualSpeed() + { + 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.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.035, true, true, false, true, 12) + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(1.34, 80.0, 0.5), + DeltaMovement = new Vec3d(0.156, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile( + current, + new Location(1.34, 80.0, 0.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.Brake, profile); + } + + [Fact] + public void ChooseGroundProfile_PicksCarry_WhenPrepareJumpNeedsRunUpSpeed() + { + 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.12, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(1.02, 80.0, 0.5), + DeltaMovement = new Vec3d(0.086, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile( + current, + new Location(1.02, 80.0, 0.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.Carry, profile); + } + + [Fact] + public void ChooseAirProfile_PicksRelease_WhenLandingNeedsSlowStableEntry() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 108, max: 126); + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.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 physics = new PlayerPhysics + { + Position = new Vec3d(123.06, 80.92, 110.5), + DeltaMovement = new Vec3d(0.31, 0.0, 0.0), + OnGround = false, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile( + current, + new Location(123.06, 80.92, 110.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.AirRelease, profile); + } + + [Fact] + public void ChooseAirProfile_KeepsForward_WhenThreeBlockLandingRecoveryIsStillShort() + { + 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, 123, 79, 110); + FlatWorldTestBuilder.SetSolid(world, 123, 79, 111); + + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.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 physics = new PlayerPhysics + { + Position = new Vec3d(122.13, 81.02, 110.5), + DeltaMovement = new Vec3d(0.1798, -0.2277, 0.0), + OnGround = false, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile( + current, + new Location(122.13, 81.02, 110.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.AirHoldForward, profile); + } + + [Fact] + public void ChooseGroundProfile_PicksCarry_WhenFinalDescendHasNotClearedUpperSupport() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 360, max: 369); + FlatWorldTestBuilder.ClearBox(world, 360, 79, 358, 369, 85, 362); + FlatWorldTestBuilder.FillSolid(world, 362, 84, 359, 362, 84, 361); + FlatWorldTestBuilder.FillSolid(world, 363, 83, 359, 363, 83, 361); + FlatWorldTestBuilder.FillSolid(world, 364, 82, 359, 364, 82, 361); + FlatWorldTestBuilder.FillSolid(world, 365, 81, 359, 365, 81, 361); + FlatWorldTestBuilder.FillSolid(world, 366, 80, 359, 366, 80, 361); + FlatWorldTestBuilder.FillSolid(world, 367, 79, 359, 367, 79, 361); + + var current = new PathSegment + { + Start = new Location(366.5, 81, 360.5), + End = new Location(367.5, 80, 360.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.FinalStop, + ExitHints = new PathTransitionHints(1, 0, 0.0, 0.02, true, true, false, false, 12) + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(367.2316, 81.0, 360.4698), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile( + current, + new Location(367.2316, 81.0, 360.4698), + physics, + world); + + Assert.Equal(TransitionInputProfile.Carry, profile); + } +} diff --git a/MinecraftClient.Tests/Pathing/Moves/MoveDescendTests.cs b/MinecraftClient.Tests/Pathing/Moves/MoveDescendTests.cs new file mode 100644 index 0000000000..732df4bffc --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Moves/MoveDescendTests.cs @@ -0,0 +1,104 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves; +using MinecraftClient.Pathing.Moves.Impl; +using MinecraftClient.Tests.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +public sealed class MoveDescendTests +{ + private const int FloorY = 79; + + private static CalculationContext BuildContext(World world) + => new(world, allowParkour: true, allowParkourAscend: true); + + [Fact] + public void Accepts1BlockStepDown() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + // Raise the source column by one so a +X step descends 1 block. + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 1, 0); + + var ctx = BuildContext(world); + var move = new MoveDescend(1, 0); + var result = default(MoveResult); + + // Source feet block is FloorY+2, destination feet block is FloorY+1. + move.Calculate(ctx, 0, FloorY + 2, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(1, result.DestX); + Assert.Equal(FloorY + 1, result.DestY); + } + + /// + /// Regression: when the landing column is itself solid at y-1 (e.g. a + /// 2-block-thick platform top), MoveDescend must reject the move. + /// Previously the simple 1-block branch only checked the y-2 floor and the + /// y / y+1 body-clearance at the destination, so A* emitted a Descend that + /// the bot could never execute (it just walked onto the solid y-1 block at + /// the same feet level), producing an infinite replan loop in live play. + /// + [Fact] + public void Rejects1BlockDescendIntoSolidLandingColumn() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + // Bot stands on source pillar (0, FloorY+1), feet at FloorY+2. + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 1, 0); + // Destination column is ALSO solid at the feet-landing level (y-1 of source). + // Concretely: (1, FloorY+1) is stone, (1, FloorY) is stone, and the flat + // floor under that is still there too. There is no valid 1-block drop. + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 0); + + var ctx = BuildContext(world); + var move = new MoveDescend(1, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 2, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Accepts2BlockDrop() + { + // Two-tier setup: source pillar at y=FloorY+2, destination floor at y=FloorY. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 1, 0); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 2, 0); + + var ctx = BuildContext(world); + var move = new MoveDescend(1, 0); + var result = default(MoveResult); + + // Source feet block is FloorY+3, destination column drops to FloorY+1 floor. + move.Calculate(ctx, 0, FloorY + 3, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(1, result.DestX); + Assert.Equal(FloorY + 1, result.DestY); + } + + [Fact] + public void RejectsMultiBlockDropWhenFlightColumnIsBlocked() + { + // Source pillar at y=FloorY+2, but destination column has a solid + // block at y-1 that blocks the fall path entirely. The bot cannot + // enter the destination column at all. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 1, 0); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY + 2, 0); + // Blocker: (1, FloorY+2) is solid -- this is the y-1 of the source feet. + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 2, 0); + + var ctx = BuildContext(world); + var move = new MoveDescend(1, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 3, 0, ref result); + + Assert.True(result.IsImpossible); + } +} diff --git a/MinecraftClient.Tests/Pathing/Moves/MoveJumpDiagonalAscendTests.cs b/MinecraftClient.Tests/Pathing/Moves/MoveJumpDiagonalAscendTests.cs new file mode 100644 index 0000000000..b3520d7e20 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Moves/MoveJumpDiagonalAscendTests.cs @@ -0,0 +1,109 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves; +using MinecraftClient.Pathing.Moves.Impl; +using MinecraftClient.Tests.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +/// +/// Regression tests for the Baritone-parity cardinal-split gate in +/// 's diagonal Ascend branch. When a cardinal +/// fallback (cardinal Walk into the dx or dz shoulder + a cardinal Ascend +/// from there) exists, the diagonal Ascend must be rejected: it has no +/// physical way to redirect the preceding segment's axis-aligned ground +/// momentum into the diagonal in 2 handoff ticks, so executing it +/// overshoots the target and loops on replan. +/// +public sealed class MoveJumpDiagonalAscendTests +{ + private const int FloorY = 79; + + private static CalculationContext BuildContext(World world) + => new(world, allowParkour: true, allowParkourAscend: true); + + [Fact] + public void RejectsDiagonalAscendWhenCardinalSplitIsWalkable() + { + // Flat floor at FloorY, so the cardinal shoulders at (1, FloorY, 0) + // and (0, FloorY, 1) both have solid ground. The ascend target is a + // 1-block riser on the diagonal corner at (1, FloorY+1, 1). Either + // "walk +X first, then cardinal Ascend +Z+Y" or "walk +Z first, then + // cardinal Ascend +X+Y" produces a stable 2-step plan, so the direct + // diagonal Ascend must be rejected. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 1); + + var ctx = BuildContext(world); + var move = MoveJump.DiagonalAscend(1, 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void AcceptsDiagonalAscendWhenBothCardinalShouldersLackFloor() + { + // Island configuration: the source pillar and the diagonal ascend + // riser are the only walk-on surfaces near the bot. The cardinal + // shoulders are open air, so no cardinal Walk + cardinal Ascend + // split exists and the diagonal Ascend is the genuine only option. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(1, FloorY, 0), Block.Air); + world.SetBlock(new Location(0, FloorY, 1), Block.Air); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 1); + + var ctx = BuildContext(world); + var move = MoveJump.DiagonalAscend(1, 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(1, result.DestX); + Assert.Equal(FloorY + 2, result.DestY); + Assert.Equal(1, result.DestZ); + } + + [Fact] + public void RejectsDiagonalAscendWhenOnlyOneCardinalShoulderHasFloor() + { + // Only the +X shoulder has floor support; the +Z shoulder is open + // air. Even a single viable cardinal split is enough for Baritone's + // gate to forbid the diagonal Ascend, because A* can simply take + // "walk +X then cardinal Ascend +Z+Y" instead. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(0, FloorY, 1), Block.Air); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 1); + + var ctx = BuildContext(world); + var move = MoveJump.DiagonalAscend(1, 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void CardinalAscendStillAcceptedOnFlatFloor() + { + // Sanity: the gate must not touch cardinal Ascend. A plain +X Ascend + // onto a 1-block riser on flat floor should still plan as before. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 0); + + var ctx = BuildContext(world); + var move = MoveJump.Ascend(1, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(1, result.DestX); + Assert.Equal(FloorY + 2, result.DestY); + } +} diff --git a/MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs b/MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs new file mode 100644 index 0000000000..ad46b188cd --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs @@ -0,0 +1,361 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Moves.Impl; +using MinecraftClient.Tests.Pathing.Execution; +using System.Reflection; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +public sealed class MoveParkourTests +{ + private const int FloorY = 79; + + private static CalculationContext BuildContext(World world) + => new(world, allowParkour: true, allowParkourAscend: true); + + private static void SetPreviousMoveType(CalculationContext ctx, MoveType moveType) + { + PropertyInfo? property = typeof(CalculationContext).GetProperty( + nameof(CalculationContext.PreviousMoveType), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + MethodInfo? setter = property?.SetMethod; + Assert.NotNull(setter); + setter!.Invoke(ctx, [moveType]); + } + + [Fact] + public void Rejects3x1JumpWhenRunUpMissing() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(-1, FloorY, 0), Block.Air); + var ctx = BuildContext(world); + var move = MoveJump.Parkour(3, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Accepts2x1GapWithClearTakeoff() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(1, FloorY, 0), Block.Air); + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(2, result.DestX); + } + + [Fact] + public void Accepts2x1Gap_TagsDefaultParkourProfile() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(1, FloorY, 0), Block.Air); + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(ParkourProfile.Default, result.ParkourProfile); + } + + [Fact] + public void Rejects2x1WhenAdjacentBlockIsStillWalkable() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Accepts2x1GapWithSingleSideWall() + { + // Cardinal 2 c2c flat parkour with a wall along ONE lateral side + // (z=-1) and clear air on the other (z=+1). The bot's footprint at + // z=0.5 stays z=[0.2,0.8], so the z=-1 wall (occupies z=[-1,0]) is + // 0.2 m clear of the bot under on-axis yaw. The previous check + // rejected this for safety; the relaxed gate accepts as long as at + // least one lateral side is passable. Mirrors the live scenario + // where breaking a head-height obstruction in a corridor leaves a + // jump-over-the-gap option as the only reachable route. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -1, FloorY, -2, 4, FloorY + 4, 2); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, -1); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 2, -1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 1, -1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 2, -1); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(2, result.DestX); + Assert.Equal(FloorY + 1, result.DestY); + Assert.Equal(0, result.DestZ); + } + + [Fact] + public void Rejects2x1GapInsideFullyWalledTunnel() + { + // Cardinal 2 c2c parkour with walls on BOTH lateral sides at body + // and head height. With no lateral bail-out margin, an executor + // yaw drift of >5 degrees during the arc can clip a wall, so the + // planner still rejects this shape. Guards against accidentally + // turning the relaxed-gate into "accept everything cardinal". + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -1, FloorY, -2, 4, FloorY + 4, 2); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, -1); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 2, -1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 1, -1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 2, -1); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 1, 1); + FlatWorldTestBuilder.SetSolid(world, 1, FloorY + 2, 1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 1, 1); + FlatWorldTestBuilder.SetSolid(world, 2, FloorY + 2, 1); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Accepts2x0Plus1AscendOverHeadObstructionGap() + { + // Live regression: bot at (256,127,225) with floor (256,126,225) and + // a head-height stone at (255,128,225). After breaking the stone, + // the bot should plan a +1 ascend cardinal sprint jump straight to + // (254,128,225). One lateral side (z=224) is a continuous wall, the + // other (z=226) is open. The gap column (255,*,225) and the cell + // beyond the landing (253,128,225) used to be rejected by + // HasCardinalSideClearance and HasLandingOvershootClearance. + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -2, FloorY, -2, 4, FloorY + 4, 2); + + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, -2, FloorY + 1, 0); + + FlatWorldTestBuilder.SetSolid(world, -3, FloorY + 1, 0); + FlatWorldTestBuilder.SetSolid(world, -3, FloorY + 2, 0); + FlatWorldTestBuilder.SetSolid(world, -3, FloorY + 3, 0); + + for (int dx = -3; dx <= 1; dx++) + { + FlatWorldTestBuilder.SetSolid(world, dx, FloorY + 1, -1); + FlatWorldTestBuilder.SetSolid(world, dx, FloorY + 2, -1); + } + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(-2, 0, yDelta: 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible, "+1 ascend cardinal 2 c2c should plan past a single-side wall and a wall-bookended landing"); + Assert.Equal(-2, result.DestX); + Assert.Equal(FloorY + 2, result.DestY); + Assert.Equal(0, result.DestZ); + } + + [Fact] + public void RejectsDiagonalWhenShoulderBlocked() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(1, FloorY + 1, 0), new Block(1)); + world.SetBlock(new Location(1, FloorY + 2, 0), new Block(1)); + var ctx = BuildContext(world); + var move = MoveJump.Parkour(1, 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void AcceptsCarried4x1DescendingGapFromSingleBlockLanding() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -2, FloorY - 2, -1, 6, FloorY + 4, 1); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 4, FloorY - 1, 0); + + var ctx = BuildContext(world); + SetPreviousMoveType(ctx, MoveType.Parkour); + var move = MoveJump.Parkour(4, 0, yDelta: -1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(4, result.DestX); + Assert.Equal(FloorY, result.DestY); + } + + [Fact] + public void Rejects4x1AscendingGapEvenWithRunway() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -2, FloorY, -1, 6, FloorY + 4, 1); + FlatWorldTestBuilder.FillSolid(world, -2, FloorY, 0, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 4, FloorY + 1, 0); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(4, 0, yDelta: 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Rejects6x1DescendingGapEvenWithRunway() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -3, FloorY - 1, -1, 8, FloorY + 4, 1); + FlatWorldTestBuilder.FillSolid(world, -3, FloorY, 0, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 6, FloorY - 1, 0); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(6, 0, yDelta: -1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Rejects6x2DescendingGapEvenWithRunway() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -3, FloorY - 2, -1, 8, FloorY + 4, 1); + FlatWorldTestBuilder.FillSolid(world, -3, FloorY, 0, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 6, FloorY - 2, 0); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(6, 0, yDelta: -2); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void RejectsCarried6x1DescendingGapFromSingleBlockLanding() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -2, FloorY - 1, -1, 8, FloorY + 4, 1); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 6, FloorY - 1, 0); + + var ctx = BuildContext(world); + SetPreviousMoveType(ctx, MoveType.Parkour); + var move = MoveJump.Parkour(6, 0, yDelta: -1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void RejectsCarried6x2DescendingGapFromSingleBlockLanding() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -2, FloorY - 2, -1, 8, FloorY + 4, 1); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + FlatWorldTestBuilder.SetSolid(world, 6, FloorY - 2, 0); + + var ctx = BuildContext(world); + SetPreviousMoveType(ctx, MoveType.Parkour); + var move = MoveJump.Parkour(6, 0, yDelta: -2); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + // Diagonal ascending parkour: +1 block up with diagonal offset, covers + // the corner-step-up case seen in stepped pyramids where a straight + // MoveSidewallParkour would demand an adjacent wall that isn't present. + // Short (sqrt(5)) ascends work from a lone overhang block because a + // cold-start sprint jump reaches ~2.5 blocks horizontally; longer + // diagonals such as (2,2) require a runway and are exercised separately. + [Theory] + [InlineData(2, 1)] + [InlineData(1, 2)] + public void AcceptsDiagonalAscendingParkour_FromLoneStart(int dx, int dz) + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -5, FloorY, -5, 10, FloorY + 5, 10); + + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + int destFloorY = FloorY + 1; + FlatWorldTestBuilder.SetSolid(world, dx, destFloorY, dz); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(dx, dz, yDelta: 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible, $"diagonal ascend ({dx},{dz},+1) should plan from lone start"); + Assert.Equal(dx, result.DestX); + Assert.Equal(destFloorY + 1, result.DestY); + Assert.Equal(dz, result.DestZ); + } + + [Fact] + public void AcceptsDiagonalAscendingParkour_2x2_WithRunway() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + FlatWorldTestBuilder.ClearBox(world, -5, FloorY, -5, 10, FloorY + 5, 10); + + // Diagonal runway behind the jump (opposite the jump direction) + // so HasRunUp's back-step check at (-1,-1) succeeds. + FlatWorldTestBuilder.SetSolid(world, -1, FloorY, -1); + FlatWorldTestBuilder.SetSolid(world, 0, FloorY, 0); + int destFloorY = FloorY + 1; + FlatWorldTestBuilder.SetSolid(world, 2, destFloorY, 2); + + var ctx = BuildContext(world); + var move = MoveJump.Parkour(2, 2, yDelta: 1); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible, "(2,2,+1) should plan with a straight runway behind the jump"); + Assert.Equal(2, result.DestX); + Assert.Equal(destFloorY + 1, result.DestY); + Assert.Equal(2, result.DestZ); + } +} diff --git a/MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs b/MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs new file mode 100644 index 0000000000..4d1f6fcab0 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs @@ -0,0 +1,135 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves.Impl; +using MinecraftClient.Tests.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +public sealed class MoveSidewallParkourTests +{ + [Theory] + [InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)] + [InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)] + public void Calculate_LongDescendStaticEntry_RejectsWithoutPreparedRunup(string scenarioId, int gap, int wallOffset) + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY: -1, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = MoveJump.Sidewall(dx: -1, dz: gap, yDelta: -1); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.True(result.IsImpossible, scenarioId); + } + + [Theory] + [InlineData("sidewall-flat-gap2-wo0", 2, 0, 0)] + [InlineData("sidewall-flat-gap3-wo1", 3, 0, 1)] + [InlineData("sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0)] + [InlineData("sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1)] + [InlineData("sidewall-descend-gap2-dy-1-wo0", 2, -1, 0)] + [InlineData("sidewall-descend-gap3-dy-1-wo1", 3, -1, 1)] + [InlineData("sidewall-descend-gap2-dy-2-wo0", 2, -2, 0)] + [InlineData("sidewall-descend-gap3-dy-2-wo1", 3, -2, 1)] + public void Calculate_AcceptsTheoryAllowedCases(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = MoveJump.Sidewall(dx: -1, dz: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.False(result.IsImpossible, scenarioId); + Assert.Equal(ParkourProfile.Sidewall, result.ParkourProfile); + } + + [Theory] + [InlineData("sidewall-flat-gap5-wo0", 5, 0, 0)] + [InlineData("sidewall-flat-gap5-wo1", 5, 0, 1)] + [InlineData("sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0)] + [InlineData("sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1)] + [InlineData("sidewall-descend-gap6-dy-1-wo0", 6, -1, 0)] + [InlineData("sidewall-descend-gap6-dy-2-wo1", 6, -2, 1)] + public void Calculate_RejectsTheoryForbiddenCases(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = MoveJump.Sidewall(dx: -1, dz: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.True(result.IsImpossible, scenarioId); + } + + // Scenarios captured from the staircase / step-pyramid image where the start + // block is a lone, overhanging tread with no 2-block runway behind it. + // Physics allows a cold-start sprint-jump to clear ~3 blocks horizontally, + // so short sidewall gaps should still plan even without a runway. + [Theory] + [InlineData("sidewall-lone-start-flat-gap2-wo0", 2, 0, 0)] + [InlineData("sidewall-lone-start-flat-gap3-wo0", 3, 0, 0)] + [InlineData("sidewall-lone-start-flat-gap2-wo1", 2, 0, 1)] + [InlineData("sidewall-lone-start-ascend-gap2-dy+1-wo0", 2, 1, 0)] + [InlineData("sidewall-lone-start-descend-gap2-dy-1-wo0", 2, -1, 0)] + [InlineData("sidewall-lone-start-descend-gap3-dy-1-wo0", 3, -1, 0)] + public void Calculate_AcceptsLoneStart_ShortSidewallJumps(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = BuildLoneStartWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = MoveJump.Sidewall(dx: -1, dz: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.False(result.IsImpossible, scenarioId); + Assert.Equal(ParkourProfile.Sidewall, result.ParkourProfile); + } + + [Theory] + [InlineData("sidewall-lone-start-flat-gap4-wo0", 4, 0, 0)] + [InlineData("sidewall-lone-start-ascend-gap3-dy+1-wo0", 3, 1, 0)] + [InlineData("sidewall-lone-start-descend-gap4-dy-1-wo0", 4, -1, 0)] + public void Calculate_RejectsLoneStart_LongSidewallJumps(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = BuildLoneStartWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = MoveJump.Sidewall(dx: -1, dz: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.True(result.IsImpossible, scenarioId); + } + + private static World BuildLoneStartWorld(int gap, int deltaY, int wallOffset) + { + const int startX = 100; + const int startY = 80; + const int startZ = 100; + int floorY = startY - 1; + int landX = startX - 1; + int landY = startY + deltaY; + int landZ = startZ + gap; + + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: 80, max: landZ + 8); + FlatWorldTestBuilder.ClearBox(world, 90, 70, 90, 110, 96, landZ + 8); + + FlatWorldTestBuilder.SetSolid(world, startX, floorY, startZ); + + FlatWorldTestBuilder.FillSolid( + world, + landX, + Math.Min(floorY, landY - 1), + startZ, + landX, + Math.Max(floorY, landY - 1) + 7, + startZ + wallOffset); + + FlatWorldTestBuilder.SetSolid(world, landX, landY - 1, landZ); + + return world; + } +} diff --git a/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json b/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json new file mode 100644 index 0000000000..2d59afbf32 --- /dev/null +++ b/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json @@ -0,0 +1,1376 @@ +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Diagonal", + "startBlock": { + "x": 171, + "y": 80, + "z": 160 + }, + "endBlock": { + "x": 172, + "y": 80, + "z": 161 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 172, + "y": 80, + "z": 161 + }, + "endBlock": { + "x": 173, + "y": 80, + "z": 162 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 173, + "y": 80, + "z": 162 + }, + "endBlock": { + "x": 174, + "y": 80, + "z": 162 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 174, + "y": 80, + "z": 162 + }, + "endBlock": { + "x": 175, + "y": 81, + "z": 162 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 175, + "y": 81, + "z": 162 + }, + "endBlock": { + "x": 176, + "y": 82, + "z": 162 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 176, + "y": 82, + "z": 162 + }, + "endBlock": { + "x": 177, + "y": 83, + "z": 162 + } + } + ] + }, + { + "scenarioId": "same-move-ascend-staircase", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Ascend", + "startBlock": { + "x": 340, + "y": 80, + "z": 340 + }, + "endBlock": { + "x": 341, + "y": 81, + "z": 340 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 341, + "y": 81, + "z": 340 + }, + "endBlock": { + "x": 342, + "y": 82, + "z": 340 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 342, + "y": 82, + "z": 340 + }, + "endBlock": { + "x": 343, + "y": 83, + "z": 340 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 343, + "y": 83, + "z": 340 + }, + "endBlock": { + "x": 344, + "y": 84, + "z": 340 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 344, + "y": 84, + "z": 340 + }, + "endBlock": { + "x": 345, + "y": 85, + "z": 340 + } + } + ] + }, + { + "scenarioId": "same-move-descend-staircase", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Descend", + "startBlock": { + "x": 362, + "y": 85, + "z": 360 + }, + "endBlock": { + "x": 364, + "y": 83, + "z": 360 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 364, + "y": 83, + "z": 360 + }, + "endBlock": { + "x": 366, + "y": 81, + "z": 360 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 366, + "y": 81, + "z": 360 + }, + "endBlock": { + "x": 367, + "y": 80, + "z": 360 + } + } + ] + }, + { + "scenarioId": "rejected-3x1-invalid-goal", + "expectedStatus": "Failed", + "segments": [] + }, + { + "scenarioId": "repeated-cardinal-parkour-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Parkour", + "startBlock": { + "x": 580, + "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, + "z": 580 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 584, + "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, + "z": 580 + } + } + ] + }, + { + "scenarioId": "repeated-diagonal-parkour-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Parkour", + "startBlock": { + "x": 600, + "y": 80, + "z": 600 + }, + "endBlock": { + "x": 602, + "y": 80, + "z": 602 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 602, + "y": 80, + "z": 602 + }, + "endBlock": { + "x": 604, + "y": 80, + "z": 604 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 604, + "y": 80, + "z": 604 + }, + "endBlock": { + "x": 606, + "y": 80, + "z": 606 + } + } + ] + }, + { + "scenarioId": "obstructed-parkour-l-turns", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Parkour", + "startBlock": { + "x": 620, + "y": 80, + "z": 620 + }, + "endBlock": { + "x": 622, + "y": 80, + "z": 621 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 622, + "y": 80, + "z": 621 + }, + "endBlock": { + "x": 626, + "y": 80, + "z": 622 + } + } + ] + }, + { + "scenarioId": "vertical-jump-mix", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Parkour", + "startBlock": { + "x": 640, + "y": 80, + "z": 620 + }, + "endBlock": { + "x": 642, + "y": 81, + "z": 620 + } + }, + { + "moveType": "Descend", + "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, + "z": 620 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 646, + "y": 81, + "z": 620 + }, + "endBlock": { + "x": 648, + "y": 80, + "z": 620 + } + } + ] + }, + { + "scenarioId": "diagonal-vertical-mix", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Ascend", + "startBlock": { + "x": 680, + "y": 80, + "z": 620 + }, + "endBlock": { + "x": 681, + "y": 81, + "z": 621 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 681, + "y": 81, + "z": 621 + }, + "endBlock": { + "x": 683, + "y": 81, + "z": 623 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 683, + "y": 81, + "z": 623 + }, + "endBlock": { + "x": 684, + "y": 80, + "z": 624 + } + } + ] + }, + { + "scenarioId": "turn-density-alternating-traverse-diagonal-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Diagonal", + "startBlock": { + "x": 460, + "y": 80, + "z": 460 + }, + "endBlock": { + "x": 461, + "y": 80, + "z": 461 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 461, + "y": 80, + "z": 461 + }, + "endBlock": { + "x": 462, + "y": 80, + "z": 462 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 462, + "y": 80, + "z": 462 + }, + "endBlock": { + "x": 463, + "y": 80, + "z": 463 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 463, + "y": 80, + "z": 463 + }, + "endBlock": { + "x": 464, + "y": 80, + "z": 464 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 464, + "y": 80, + "z": 464 + }, + "endBlock": { + "x": 465, + "y": 80, + "z": 465 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 465, + "y": 80, + "z": 465 + }, + "endBlock": { + "x": 466, + "y": 80, + "z": 466 + } + } + ] + }, + { + "scenarioId": "mixed-traverse-ascend-parkour-descend", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 440, + "y": 80, + "z": 440 + }, + "endBlock": { + "x": 441, + "y": 80, + "z": 440 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 441, + "y": 80, + "z": 440 + }, + "endBlock": { + "x": 442, + "y": 81, + "z": 440 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 442, + "y": 81, + "z": 440 + }, + "endBlock": { + "x": 443, + "y": 82, + "z": 440 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 443, + "y": 82, + "z": 440 + }, + "endBlock": { + "x": 444, + "y": 82, + "z": 440 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 444, + "y": 82, + "z": 440 + }, + "endBlock": { + "x": 446, + "y": 82, + "z": 440 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 446, + "y": 82, + "z": 440 + }, + "endBlock": { + "x": 448, + "y": 80, + "z": 440 + } + } + ] + }, + { + "scenarioId": "same-move-aligned-parkour-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Parkour", + "startBlock": { + "x": 380, + "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, + "z": 380 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 384, + "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, + "z": 380 + } + } + ] + }, + { + "scenarioId": "mixed-diagonal-ascend-traverse-descend", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Diagonal", + "startBlock": { + "x": 420, + "y": 80, + "z": 420 + }, + "endBlock": { + "x": 421, + "y": 80, + "z": 421 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 421, + "y": 80, + "z": 421 + }, + "endBlock": { + "x": 422, + "y": 80, + "z": 422 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 422, + "y": 80, + "z": 422 + }, + "endBlock": { + "x": 423, + "y": 81, + "z": 422 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 423, + "y": 81, + "z": 422 + }, + "endBlock": { + "x": 424, + "y": 82, + "z": 422 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 424, + "y": 82, + "z": 422 + }, + "endBlock": { + "x": 425, + "y": 82, + "z": 422 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 425, + "y": 82, + "z": 422 + }, + "endBlock": { + "x": 426, + "y": 82, + "z": 422 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 426, + "y": 82, + "z": 422 + }, + "endBlock": { + "x": 428, + "y": 80, + "z": 422 + } + } + ] + }, + { + "scenarioId": "speed-carry-repeated-traverse-ascend", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 480, + "y": 80, + "z": 480 + }, + "endBlock": { + "x": 481, + "y": 80, + "z": 480 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 481, + "y": 80, + "z": 480 + }, + "endBlock": { + "x": 482, + "y": 81, + "z": 480 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 482, + "y": 81, + "z": 480 + }, + "endBlock": { + "x": 483, + "y": 81, + "z": 480 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 483, + "y": 81, + "z": 480 + }, + "endBlock": { + "x": 484, + "y": 82, + "z": 480 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 484, + "y": 82, + "z": 480 + }, + "endBlock": { + "x": 485, + "y": 82, + "z": 480 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 485, + "y": 82, + "z": 480 + }, + "endBlock": { + "x": 486, + "y": 83, + "z": 480 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 486, + "y": 83, + "z": 480 + }, + "endBlock": { + "x": 487, + "y": 83, + "z": 480 + } + }, + { + "moveType": "Ascend", + "startBlock": { + "x": 487, + "y": 83, + "z": 480 + }, + "endBlock": { + "x": 488, + "y": 84, + "z": 480 + } + } + ] + }, + { + "scenarioId": "speed-carry-repeated-traverse-descend", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 500, + "y": 83, + "z": 500 + }, + "endBlock": { + "x": 501, + "y": 83, + "z": 500 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 501, + "y": 83, + "z": 500 + }, + "endBlock": { + "x": 504, + "y": 81, + "z": 500 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 504, + "y": 81, + "z": 500 + }, + "endBlock": { + "x": 505, + "y": 81, + "z": 500 + } + }, + { + "moveType": "Descend", + "startBlock": { + "x": 505, + "y": 81, + "z": 500 + }, + "endBlock": { + "x": 507, + "y": 80, + "z": 500 + } + } + ] + }, + { + "scenarioId": "speed-carry-repeated-traverse-parkour", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 520, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 521, + "y": 80, + "z": 520 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 521, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 523, + "y": 80, + "z": 520 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 523, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 524, + "y": 80, + "z": 520 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 524, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 526, + "y": 80, + "z": 520 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 526, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 527, + "y": 80, + "z": 520 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 527, + "y": 80, + "z": 520 + }, + "endBlock": { + "x": 529, + "y": 80, + "z": 520 + } + } + ] + }, + { + "scenarioId": "same-move-diagonal-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Diagonal", + "startBlock": { + "x": 320, + "y": 80, + "z": 320 + }, + "endBlock": { + "x": 321, + "y": 80, + "z": 321 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 321, + "y": 80, + "z": 321 + }, + "endBlock": { + "x": 322, + "y": 80, + "z": 322 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 322, + "y": 80, + "z": 322 + }, + "endBlock": { + "x": 323, + "y": 80, + "z": 323 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 323, + "y": 80, + "z": 323 + }, + "endBlock": { + "x": 324, + "y": 80, + "z": 324 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 324, + "y": 80, + "z": 324 + }, + "endBlock": { + "x": 325, + "y": 80, + "z": 325 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 325, + "y": 80, + "z": 325 + }, + "endBlock": { + "x": 326, + "y": 80, + "z": 326 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 326, + "y": 80, + "z": 326 + }, + "endBlock": { + "x": 327, + "y": 80, + "z": 327 + } + } + ] + }, + { + "scenarioId": "same-move-straight-traverse-chain", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 300, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 301, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 301, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 302, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 302, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 303, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 303, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 304, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 304, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 305, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 305, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 306, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 306, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 307, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 307, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 308, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 308, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 309, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 309, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 310, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 310, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 311, + "y": 80, + "z": 300 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 311, + "y": 80, + "z": 300 + }, + "endBlock": { + "x": 312, + "y": 80, + "z": 300 + } + } + ] + }, + { + "scenarioId": "mixed-traverse-turn-parkour-turn-traverse", + "expectedStatus": "Success", + "segments": [ + { + "moveType": "Traverse", + "startBlock": { + "x": 400, + "y": 80, + "z": 400 + }, + "endBlock": { + "x": 401, + "y": 80, + "z": 400 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 401, + "y": 80, + "z": 400 + }, + "endBlock": { + "x": 402, + "y": 80, + "z": 401 + } + }, + { + "moveType": "Parkour", + "startBlock": { + "x": 402, + "y": 80, + "z": 401 + }, + "endBlock": { + "x": 404, + "y": 80, + "z": 402 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 404, + "y": 80, + "z": 402 + }, + "endBlock": { + "x": 405, + "y": 80, + "z": 402 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 405, + "y": 80, + "z": 402 + }, + "endBlock": { + "x": 406, + "y": 80, + "z": 403 + } + }, + { + "moveType": "Diagonal", + "startBlock": { + "x": 406, + "y": 80, + "z": 403 + }, + "endBlock": { + "x": 407, + "y": 80, + "z": 404 + } + }, + { + "moveType": "Traverse", + "startBlock": { + "x": 407, + "y": 80, + "z": 404 + }, + "endBlock": { + "x": 408, + "y": 80, + "z": 404 + } + } + ] + } +] diff --git a/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json b/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json new file mode 100644 index 0000000000..dc0978a4c5 --- /dev/null +++ b/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json @@ -0,0 +1,619 @@ +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedTotalTicks": 0, + "maxTotalTicks": 0, + "segments": [ + { + "moveType": "Diagonal", + "expectedTicks": 0, + "maxTicks": 0 + }, + { + "moveType": "Diagonal", + "expectedTicks": 0, + "maxTicks": 0 + }, + { + "moveType": "Traverse", + "expectedTicks": 0, + "maxTicks": 0 + }, + { + "moveType": "Ascend", + "expectedTicks": 0, + "maxTicks": 0 + }, + { + "moveType": "Ascend", + "expectedTicks": 0, + "maxTicks": 0 + }, + { + "moveType": "Ascend", + "expectedTicks": 0, + "maxTicks": 0 + } + ] + }, + { + "scenarioId": "same-move-ascend-staircase", + "expectedTotalTicks": 60, + "maxTotalTicks": 74, + "segments": [ + { + "moveType": "Ascend", + "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, + "segments": [ + { + "moveType": "Descend", + "expectedTicks": 24, + "maxTicks": 29 + }, + { + "moveType": "Descend", + "expectedTicks": 25, + "maxTicks": 30 + }, + { + "moveType": "Descend", + "expectedTicks": 12, + "maxTicks": 15 + } + ] + }, + { + "scenarioId": "rejected-3x1-invalid-goal", + "expectedTotalTicks": 0, + "maxTotalTicks": 0, + "segments": [] + }, + { + "scenarioId": "repeated-cardinal-parkour-chain", + "expectedTotalTicks": 123, + "maxTotalTicks": 150, + "segments": [ + { + "moveType": "Parkour", + "expectedTicks": 46, + "maxTicks": 56 + }, + { + "moveType": "Parkour", + "expectedTicks": 41, + "maxTicks": 50 + }, + { + "moveType": "Parkour", + "expectedTicks": 20, + "maxTicks": 24 + }, + { + "moveType": "Parkour", + "expectedTicks": 16, + "maxTicks": 20 + } + ] + }, + { + "scenarioId": "repeated-diagonal-parkour-chain", + "expectedTotalTicks": 67, + "maxTotalTicks": 82, + "segments": [ + { + "moveType": "Parkour", + "expectedTicks": 14, + "maxTicks": 17 + }, + { + "moveType": "Parkour", + "expectedTicks": 36, + "maxTicks": 44 + }, + { + "moveType": "Parkour", + "expectedTicks": 17, + "maxTicks": 21 + } + ] + }, + { + "scenarioId": "obstructed-parkour-l-turns", + "expectedTotalTicks": 50, + "maxTotalTicks": 80, + "segments": [ + { + "moveType": "Parkour", + "expectedTicks": 20, + "maxTicks": 32 + }, + { + "moveType": "Parkour", + "expectedTicks": 30, + "maxTicks": 48 + } + ] + }, + { + "scenarioId": "vertical-jump-mix", + "expectedTotalTicks": 50, + "maxTotalTicks": 62, + "segments": [ + { + "moveType": "Parkour", + "expectedTicks": 13, + "maxTicks": 16 + }, + { + "moveType": "Descend", + "expectedTicks": 12, + "maxTicks": 15 + }, + { + "moveType": "Parkour", + "expectedTicks": 12, + "maxTicks": 15 + }, + { + "moveType": "Descend", + "expectedTicks": 13, + "maxTicks": 16 + } + ] + }, + { + "scenarioId": "diagonal-vertical-mix", + "expectedTotalTicks": 48, + "maxTotalTicks": 59, + "segments": [ + { + "moveType": "Ascend", + "expectedTicks": 19, + "maxTicks": 23 + }, + { + "moveType": "Parkour", + "expectedTicks": 16, + "maxTicks": 20 + }, + { + "moveType": "Descend", + "expectedTicks": 13, + "maxTicks": 16 + } + ] + }, + { + "scenarioId": "turn-density-alternating-traverse-diagonal-chain", + "expectedTotalTicks": 47, + "maxTotalTicks": 59, + "segments": [ + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 7, + "maxTicks": 9 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + } + ] + }, + { + "scenarioId": "mixed-traverse-ascend-parkour-descend", + "expectedTotalTicks": 70, + "maxTotalTicks": 88, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Ascend", + "expectedTicks": 12, + "maxTicks": 15 + }, + { + "moveType": "Ascend", + "expectedTicks": 13, + "maxTicks": 16 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Parkour", + "expectedTicks": 14, + "maxTicks": 17 + }, + { + "moveType": "Descend", + "expectedTicks": 21, + "maxTicks": 26 + } + ] + }, + { + "scenarioId": "same-move-aligned-parkour-chain", + "expectedTotalTicks": 123, + "maxTotalTicks": 150, + "segments": [ + { + "moveType": "Parkour", + "expectedTicks": 46, + "maxTicks": 56 + }, + { + "moveType": "Parkour", + "expectedTicks": 41, + "maxTicks": 50 + }, + { + "moveType": "Parkour", + "expectedTicks": 20, + "maxTicks": 24 + }, + { + "moveType": "Parkour", + "expectedTicks": 16, + "maxTicks": 20 + } + ] + }, + { + "scenarioId": "mixed-diagonal-ascend-traverse-descend", + "expectedTotalTicks": 96, + "maxTotalTicks": 120, + "segments": [ + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Ascend", + "expectedTicks": 32, + "maxTicks": 39 + }, + { + "moveType": "Ascend", + "expectedTicks": 11, + "maxTicks": 14 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Descend", + "expectedTicks": 26, + "maxTicks": 32 + } + ] + }, + { + "scenarioId": "speed-carry-repeated-traverse-ascend", + "expectedTotalTicks": 70, + "maxTotalTicks": 90, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Ascend", + "expectedTicks": 13, + "maxTicks": 16 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "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": 45, + "maxTotalTicks": 57, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Parkour", + "expectedTicks": 21, + "maxTicks": 26 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Descend", + "expectedTicks": 13, + "maxTicks": 16 + } + ] + }, + { + "scenarioId": "speed-carry-repeated-traverse-parkour", + "expectedTotalTicks": 58, + "maxTotalTicks": 73, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Parkour", + "expectedTicks": 14, + "maxTicks": 17 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Parkour", + "expectedTicks": 14, + "maxTicks": 17 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Parkour", + "expectedTicks": 15, + "maxTicks": 18 + } + ] + }, + { + "scenarioId": "same-move-diagonal-chain", + "expectedTotalTicks": 55, + "maxTotalTicks": 69, + "segments": [ + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 7, + "maxTicks": 9 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 7, + "maxTicks": 9 + }, + { + "moveType": "Diagonal", + "expectedTicks": 9, + "maxTicks": 11 + } + ] + }, + { + "scenarioId": "same-move-straight-traverse-chain", + "expectedTotalTicks": 70, + "maxTotalTicks": 94, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 5, + "maxTicks": 7 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Traverse", + "expectedTicks": 7, + "maxTicks": 9 + } + ] + }, + { + "scenarioId": "mixed-traverse-turn-parkour-turn-traverse", + "expectedTotalTicks": 60, + "maxTotalTicks": 75, + "segments": [ + { + "moveType": "Traverse", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 6, + "maxTicks": 8 + }, + { + "moveType": "Parkour", + "expectedTicks": 14, + "maxTicks": 17 + }, + { + "moveType": "Traverse", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 8, + "maxTicks": 10 + }, + { + "moveType": "Diagonal", + "expectedTicks": 9, + "maxTicks": 11 + }, + { + "moveType": "Traverse", + "expectedTicks": 7, + "maxTicks": 9 + } + ] + } +] diff --git a/MinecraftClient.sln b/MinecraftClient.sln index ebdf1f09d1..19afc906f8 100644 --- a/MinecraftClient.sln +++ b/MinecraftClient.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MccMcpStdioHarness", "Debug EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MccMcpWebPlayground", "DebugTools\MccMcpWebPlayground\MccMcpWebPlayground.csproj", "{5F620CF6-BC7D-449A-B779-2D51985059C6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinecraftClient.Tests", "MinecraftClient.Tests\MinecraftClient.Tests.csproj", "{A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,6 +73,18 @@ Global {5F620CF6-BC7D-449A-B779-2D51985059C6}.Release|x64.Build.0 = Release|Any CPU {5F620CF6-BC7D-449A-B779-2D51985059C6}.Release|x86.ActiveCfg = Release|Any CPU {5F620CF6-BC7D-449A-B779-2D51985059C6}.Release|x86.Build.0 = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|x64.Build.0 = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Debug|x86.Build.0 = Debug|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|x64.ActiveCfg = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|x64.Build.0 = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|x86.ActiveCfg = Release|Any CPU + {A6F319D6-4D0E-4D46-A31E-EF64E5F9F596}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MinecraftClient/Commands/Debug.cs b/MinecraftClient/Commands/Debug.cs index 92184f1d25..f050f2836c 100644 --- a/MinecraftClient/Commands/Debug.cs +++ b/MinecraftClient/Commands/Debug.cs @@ -78,6 +78,7 @@ private int ShowState(CmdResult r) var loc = handler.GetCurrentLocation(); sb.AppendLine($"§7{Translations.cmd_debug_state_location,-10}§f{loc.X:F2}, {loc.Y:F2}, {loc.Z:F2}"); + sb.AppendLine($"§7{Translations.cmd_debug_state_on_ground,-10}§f{(handler.GetLocalOnGround() ? "true" : "false")}"); sb.AppendLine($"§7{Translations.cmd_debug_state_tps,-10}§f{handler.GetServerTPS():F1}"); diff --git a/MinecraftClient/Commands/Goto.cs b/MinecraftClient/Commands/Goto.cs new file mode 100644 index 0000000000..3368053599 --- /dev/null +++ b/MinecraftClient/Commands/Goto.cs @@ -0,0 +1,53 @@ +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; + +namespace MinecraftClient.Commands +{ + public class Goto : Command + { + public override string CmdName => "goto"; + public override string CmdUsage => "goto "; + public override string CmdDesc => Translations.cmd_goto_desc; + + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty))) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("location", MccArguments.Location()) + .Executes(r => DoGoto(r.Source, MccArguments.GetLocation(r, "location")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(GetCmdDescTranslated()); + } + + private static int DoGoto(CmdResult r, Location goal) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + goal.ToAbsolute(current); + + // The A* search runs on a background task (see NavigateToGoal), so + // a generous budget no longer blocks the 20 TPS tick loop. Matches + // Baritone's multi-second default budget for interactive goto. + var (success, message) = handler.MoveToAStar(goal, timeoutMs: 15000); + + return r.SetAndReturn(success ? Status.Done : Status.Fail, message); + } + } +} diff --git a/MinecraftClient/Commands/PathDiag.cs b/MinecraftClient/Commands/PathDiag.cs new file mode 100644 index 0000000000..e324a5f082 --- /dev/null +++ b/MinecraftClient/Commands/PathDiag.cs @@ -0,0 +1,57 @@ +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.Pathing.Execution; +using static MinecraftClient.CommandHandler.CmdResult; + +namespace MinecraftClient.Commands +{ + /// + /// Toggles Info-level pathing diagnostics. When enabled, + /// emits the full waypoint dump of every planned/replanned path, the recent per-tick + /// trace at segment-failure time, and the failing segment's target. Used for + /// reporting pathing bugs without permanently changing the debug log level. + /// + public class PathDiag : Command + { + public override string CmdName => "pathdiag"; + public override string CmdUsage => "pathdiag [on|off]"; + public override string CmdDesc => Translations.cmd_pathdiag_desc; + + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source))) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => Toggle(r.Source)) + .Then(l => l.Literal("on") + .Executes(r => SetDiagnostics(r.Source, true))) + .Then(l => l.Literal("off") + .Executes(r => SetDiagnostics(r.Source, false))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r) + { + return r.SetAndReturn(GetCmdDescTranslated()); + } + + private static int Toggle(CmdResult r) + { + return SetDiagnostics(r, !PathSegmentManager.DiagnosticsEnabled); + } + + private static int SetDiagnostics(CmdResult r, bool enabled) + { + PathSegmentManager.DiagnosticsEnabled = enabled; + return r.SetAndReturn(Status.Done, + enabled ? Translations.cmd_pathdiag_enabled : Translations.cmd_pathdiag_disabled); + } + } +} diff --git a/MinecraftClient/Commands/Pathfind.cs b/MinecraftClient/Commands/Pathfind.cs new file mode 100644 index 0000000000..8f8bd30626 --- /dev/null +++ b/MinecraftClient/Commands/Pathfind.cs @@ -0,0 +1,50 @@ +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; + +namespace MinecraftClient.Commands +{ + public class Pathfind : Command + { + public override string CmdName => "pathfind"; + public override string CmdUsage => "pathfind "; + public override string CmdDesc => Translations.cmd_pathfind_desc; + + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source))) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("location", MccArguments.Location()) + .Executes(r => DoPathfind(r.Source, MccArguments.GetLocation(r, "location")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r) + { + return r.SetAndReturn(GetCmdDescTranslated()); + } + + private static int DoPathfind(CmdResult r, Location goal) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + goal.ToAbsolute(current); + + var (success, message) = handler.MoveToAStar(goal, timeoutMs: 10000); + + return r.SetAndReturn(success ? Status.Done : Status.Fail, message); + } + } +} diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index dc76441b0f..6d32351e3a 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -80,6 +80,7 @@ public class McClient : IMinecraftComHandler private readonly MovementInput physicsInput = new(); private bool physicsInitialized = false; private Location? pathTarget; // Current waypoint for physics-driven pathfinding + private Pathing.Execution.PathSegmentManager? pathSegmentManager; public enum MovementType { Sneak, Walk, Sprint } private int sequenceId; // User for player block synchronization (Aka. digging, placing blocks, etc..) private bool CanSendMessage = false; @@ -153,6 +154,11 @@ public enum MovementType { Sneak, Walk, Sprint } public string GetUserUuidStr() { return uuidStr; } public string GetSessionID() { return sessionid; } public Location GetCurrentLocation() { return location; } + public bool GetLocalOnGround() + { + Location current = GetCurrentLocation(); + return physicsInitialized ? playerPhysics.OnGround : Movement.IsOnGround(world, current); + } public float GetYaw() { return playerYaw; } public int GetSequenceId() { return sequenceId; } public float GetPitch() { return playerPitch; } @@ -566,6 +572,8 @@ private void ResetStateForTransfer() isUnderSlab = false; path = null; pathTarget = null; + pathSegmentManager?.Cancel(); + pathSegmentManager = null; _yaw = null; _pitch = null; LastDigPosition = null; @@ -685,6 +693,7 @@ public void OnUpdate() playerPhysics.SetPosition(location.X, location.Y, location.Z); playerPhysics.Yaw = playerYaw; playerPhysics.Pitch = playerPitch; + playerPhysics.DebugLog = msg => Log.Debug(msg); physicsInitialized = true; } @@ -1713,6 +1722,59 @@ public bool MoveTo(Location goal, bool allowUnsafe = false, bool allowDirectTele } } + /// + /// Navigate to a goal using the new A* pathfinder and template-based execution. + /// The A* search runs on a background task so the 20 TPS tick loop is + /// never blocked; the caller sees a "planning started" acknowledgement + /// and the real result is logged when + /// installs the plan + /// on a subsequent tick. + /// + public (bool success, string message) NavigateToGoal(Pathing.Goals.IGoal goal, long timeoutMs = 5000) + { + Location startPos; + lock (locationLock) + { + startPos = location; + } + + Log.Info($"[Navigate] A* search from ({(int)Math.Floor(startPos.X)},{(int)Math.Floor(startPos.Y)},{(int)Math.Floor(startPos.Z)}) to {goal}"); + + pathTarget = null; + path = null; + + pathSegmentManager?.Cancel(); + pathSegmentManager = new Pathing.Execution.PathSegmentManager( + debugLog: msg => Log.Debug(msg), + infoLog: msg => Log.Info(msg), + observer: new Pathing.Execution.Telemetry.PathExecutionLogObserver(msg => Log.Debug(msg))); + pathSegmentManager.StartNavigationAsync(goal, startPos, world, timeoutMs); + + return (true, string.Format(Translations.cmd_goto_planning, timeoutMs)); + } + + /// + /// Navigate to a block location using the new A* pathfinder and template-based execution. + /// Convenience overload that creates a GoalBlock from the location. + /// Returns a description of the result for UI feedback. + /// + public (bool success, string message) MoveToAStar(Location goal, long timeoutMs = 5000) + { + int gx = (int)Math.Floor(goal.X); + int gy = (int)Math.Floor(goal.Y); + int gz = (int)Math.Floor(goal.Z); + + lock (locationLock) + { + var ctx = new Pathing.Core.CalculationContext(world); + if (!ctx.CanWalkThrough(gx, gy, gz) && ctx.CanWalkThrough(gx, gy + 1, gz)) + gy++; + } + + var pathGoal = new Pathing.Goals.GoalBlock(gx, gy, gz); + return NavigateToGoal(pathGoal, timeoutMs); + } + /// /// Send a chat message or command to the server /// @@ -3192,52 +3254,91 @@ public void OnRespawn() } /// - /// Drive the physics engine input based on the current A* path. - /// Converts discrete waypoint pathfinding into continuous movement input. + /// Drive the physics engine input based on the current path. + /// Uses template-based PathSegmentManager when available, falls back to legacy waypoints. /// private void UpdatePathfindingInput() { physicsInput.Reset(); - // Still heading toward a target (even if path queue is empty) + // Template-based execution (new system) + if (pathSegmentManager is not null && pathSegmentManager.IsNavigating) + { + pathSegmentManager.Tick(location, playerPhysics, physicsInput, world); + playerYaw = playerPhysics.Yaw; + playerPitch = playerPhysics.Pitch; + _yaw = playerYaw; + _pitch = playerPitch; + return; + } + + // Legacy waypoint-based execution if (pathTarget is not null && ReachedWaypoint(pathTarget.Value)) + AdvanceWaypoint(); + + if (pathTarget is null && path is not null && path.Count > 0) + AdvanceWaypoint(); + + if (pathTarget is not null) { - // Arrived at current waypoint — advance to next, or finish if (path is not null && path.Count > 0) { - pathTarget = path.Dequeue(); - if (Config.Main.Advanced.MoveHeadWhileWalking) - UpdateLocation(location, pathTarget.Value + new Location(0, 1, 0)); - } - else - { - pathTarget = null; - path = null; + var target = pathTarget.Value; + double dx = target.X - location.X; + double dz = target.Z - location.Z; + double dy = target.Y - location.Y; + double horizDistSq = dx * dx + dz * dz; + + bool isVerticalWaypoint = horizDistSq < 0.5 && Math.Abs(dy) > 0.3; + if (isVerticalWaypoint) + { + var next = path.Peek(); + double ndx = next.X - target.X; + double ndz = next.Z - target.Z; + bool nextIsHorizontal = ndx * ndx + ndz * ndz > 0.3; + + if (nextIsHorizontal && Math.Abs(dy) < 1.0) + { + AdvanceWaypoint(); + } + } } + + SetInputToward(pathTarget.Value); } + } - // Need a first target from a fresh path - if (pathTarget is null && path is not null && path.Count > 0) + private void AdvanceWaypoint() + { + if (path is not null && path.Count > 0) { pathTarget = path.Dequeue(); if (Config.Main.Advanced.MoveHeadWhileWalking) UpdateLocation(location, pathTarget.Value + new Location(0, 1, 0)); } - - if (pathTarget is not null) + else { - SetInputToward(pathTarget.Value); + pathTarget = null; + path = null; } } /// /// Check if the player has approximately reached a waypoint. + /// Uses both horizontal and vertical distance for climb/descend waypoints. /// private bool ReachedWaypoint(Location target) { double dx = target.X - location.X; double dz = target.Z - location.Z; - return dx * dx + dz * dz < 0.25; // within ~0.5 blocks horizontally + double dy = target.Y - location.Y; + double horizDistSq = dx * dx + dz * dz; + + // Vertical waypoint (climbing/falling): require reaching target Y level + if (horizDistSq < 0.5 && Math.Abs(dy) > 0.8) + return false; + + return horizDistSq < 0.25 && Math.Abs(dy) < 0.8; } /// @@ -3251,7 +3352,47 @@ private void SetInputToward(Location target) double dy = target.Y - location.Y; double distSqr = dx * dx + dz * dz; - if (distSqr < 0.01) return; // Close enough horizontally + // Climbing: target is above/below with small horizontal offset + if (playerPhysics.OnClimbable && Math.Abs(dy) > 0.5 && distSqr < 1.0) + { + if (dy > 0) + { + physicsInput.Jump = true; + // Push against the wall for HorizontalCollision-triggered climbing + if (distSqr > 0.01) + { + float yaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); + if (yaw < 0) yaw += 360; + playerPhysics.Yaw = yaw; + playerYaw = yaw; + physicsInput.Forward = true; + } + else + { + physicsInput.Forward = true; + } + } + else + { + physicsInput.Sneak = false; + } + return; + } + + // Non-climbing vertical jump + if (distSqr < 0.1 && dy > 0.5 && playerPhysics.OnGround) + { + physicsInput.Jump = true; + return; + } + + if (distSqr < 0.01) + { + // Vertically aligned but need to reach different Y: set Jump when on ground + if (dy > 0.3 && playerPhysics.OnGround) + physicsInput.Jump = true; + return; + } // Calculate yaw to face target float targetYaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); @@ -3278,7 +3419,14 @@ private void SetInputToward(Location target) /// true if a movement is currently handled public bool ClientIsMoving() { - return terrainAndMovementsEnabled && locationReceived && path is not null && path.Count > 0; + if (terrainAndMovementsEnabled && locationReceived) + { + if (pathSegmentManager is not null && pathSegmentManager.IsNavigating) + return true; + if (path is not null && path.Count > 0) + return true; + } + return false; } /// @@ -3287,7 +3435,16 @@ public bool ClientIsMoving() /// Current goal of movement. Location.Zero if not set. public Location GetCurrentMovementGoal() { - return (ClientIsMoving() || path is null) ? Location.Zero : path.Last(); + if (pathSegmentManager is not null && pathSegmentManager.IsNavigating) + { + if (pathSegmentManager.Goal is Pathing.Goals.GoalBlock gb) + return new Location(gb.X + 0.5, gb.Y, gb.Z + 0.5); + } + + if (path is not null && path.Count > 0) + return path.Last(); + + return Location.Zero; } /// @@ -3298,6 +3455,9 @@ public bool CancelMovement() { bool success = ClientIsMoving(); path = null; + pathTarget = null; + pathSegmentManager?.Cancel(); + pathSegmentManager = null; return success; } @@ -3311,7 +3471,7 @@ public void SetMovementSpeed(MovementType newSpeed) { case MovementType.Sneak: // https://minecraft.wiki/w/Sneaking#Effects - Sneaking 1.31m/s - Config.Main.Advanced.MovementSpeed = 2; + Config.Main.Advanced.MovementSpeed = 1; break; case MovementType.Walk: // https://minecraft.wiki/w/Walking#Usage - Walking 4.317 m/s diff --git a/MinecraftClient/Pathing/Core/AStarPathFinder.cs b/MinecraftClient/Pathing/Core/AStarPathFinder.cs new file mode 100644 index 0000000000..520ab8f966 --- /dev/null +++ b/MinecraftClient/Pathing/Core/AStarPathFinder.cs @@ -0,0 +1,520 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using MinecraftClient.Pathing.Goals; +using MinecraftClient.Pathing.Moves; +using MinecraftClient.Pathing.Moves.Impl; + +namespace MinecraftClient.Pathing.Core +{ + public sealed class AStarPathFinder + { + private readonly record struct NodeKey(long PackedPosition, EntryPreparationState EntryPreparation); + + private readonly IMove[] _allMoves; + private readonly IMoveExpander[] _expanders; + private readonly int _totalExpanderCapacity; + private readonly int _maxChunkBorderFetch; + + public Action? DebugLog { get; set; } + + public AStarPathFinder(IMove[]? moves = null, int maxChunkBorderFetch = 64) + : this(BuildExpanders(moves), moves ?? BuildDefaultMoves(), maxChunkBorderFetch) + { + } + + public AStarPathFinder(IMoveExpander[] expanders, int maxChunkBorderFetch = 64) + : this(expanders, System.Array.Empty(), maxChunkBorderFetch) + { + } + + private AStarPathFinder(IMoveExpander[] expanders, IMove[] allMoves, int maxChunkBorderFetch) + { + _expanders = expanders; + _allMoves = allMoves; + _maxChunkBorderFetch = maxChunkBorderFetch; + + int total = 0; + for (int i = 0; i < expanders.Length; i++) + total += expanders[i].MaxNeighbors; + _totalExpanderCapacity = total; + } + + private static IMoveExpander[] BuildExpanders(IMove[]? explicitMoves) + { + if (explicitMoves is null) + { + return BuildDefaultExpanders(); + } + + // Caller supplied a specific move set (e.g. tests). Wrap it as a + // legacy expander so the old API keeps working. + return [new LegacyMoveExpander(explicitMoves)]; + } + + public static IMoveExpander[] BuildDefaultExpanders() + { + IMove[] legacyMoves = + [ + new MoveDescend(1, 0), + new MoveDescend(-1, 0), + new MoveDescend(0, 1), + new MoveDescend(0, -1), + new MoveSprintDescend(2, 0), + new MoveSprintDescend(-2, 0), + new MoveSprintDescend(0, 2), + new MoveSprintDescend(0, -2), + new MoveSprintDescend(1, 1), + new MoveSprintDescend(1, -1), + new MoveSprintDescend(-1, 1), + new MoveSprintDescend(-1, -1), + new MoveClimb(true), + new MoveClimb(false), + new MoveFall(), + ]; + + return [new JumpExpander(), new LegacyMoveExpander(legacyMoves)]; + } + + public static IMove[] BuildDefaultMoves() + { + var moves = new List(); + + int[] offsets = [1, -1]; + + // ---- jump family (all unified as MoveJump with a JumpDescriptor) ---- + + // Cardinal walk + 1-block ascend + foreach (int dx in offsets) + { + moves.Add(MoveJump.Traverse(dx, 0)); + moves.Add(MoveJump.Ascend(dx, 0)); + } + foreach (int dz in offsets) + { + moves.Add(MoveJump.Traverse(0, dz)); + moves.Add(MoveJump.Ascend(0, dz)); + } + + // Diagonal walk + diagonal ascend/descend (corner cases) + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + moves.Add(MoveJump.Diagonal(dx, dz)); + moves.Add(MoveJump.DiagonalAscend(dx, dz)); + moves.Add(MoveJump.DiagonalDescend(dx, dz)); + } + } + + // Cardinal parkour (flat / +1 ascend / -1 -2 descend) + foreach (int dx in offsets) + { + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(dx * dist, 0)); + for (int dist = 2; dist <= 3; dist++) + moves.Add(MoveJump.Parkour(dx * dist, 0, yDelta: 1)); + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(dx * dist, 0, yDelta: -1)); + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(dx * dist, 0, yDelta: -2)); + } + foreach (int dz in offsets) + { + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(0, dz * dist)); + for (int dist = 2; dist <= 3; dist++) + moves.Add(MoveJump.Parkour(0, dz * dist, yDelta: 1)); + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(0, dz * dist, yDelta: -1)); + for (int dist = 2; dist <= 5; dist++) + moves.Add(MoveJump.Parkour(0, dz * dist, yDelta: -2)); + } + + // Diagonal parkour (flat + diagonal ascending/descending) + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + moves.Add(MoveJump.Parkour(dx * 2, dz * 1)); + moves.Add(MoveJump.Parkour(dx * 1, dz * 2)); + moves.Add(MoveJump.Parkour(dx * 2, dz * 2)); + moves.Add(MoveJump.Parkour(dx * 3, dz * 1)); + moves.Add(MoveJump.Parkour(dx * 1, dz * 3)); + + moves.Add(MoveJump.Parkour(dx * 2, dz * 1, yDelta: -1)); + moves.Add(MoveJump.Parkour(dx * 1, dz * 2, yDelta: -1)); + moves.Add(MoveJump.Parkour(dx * 2, dz * 2, yDelta: -1)); + + moves.Add(MoveJump.Parkour(dx * 2, dz * 1, yDelta: 1)); + moves.Add(MoveJump.Parkour(dx * 1, dz * 2, yDelta: 1)); + moves.Add(MoveJump.Parkour(dx * 2, dz * 2, yDelta: 1)); + } + } + + // Sidewall parkour (dominant-axis sprint jumps using an inner wall) + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + foreach (int distance in new[] { 2, 3, 4, 5 }) + { + moves.Add(MoveJump.Sidewall(dx, dz * distance)); + moves.Add(MoveJump.Sidewall(dx * distance, dz)); + + if (distance <= 3) + { + moves.Add(MoveJump.Sidewall(dx, dz * distance, yDelta: 1)); + moves.Add(MoveJump.Sidewall(dx * distance, dz, yDelta: 1)); + } + + moves.Add(MoveJump.Sidewall(dx, dz * distance, yDelta: -1)); + moves.Add(MoveJump.Sidewall(dx * distance, dz, yDelta: -1)); + moves.Add(MoveJump.Sidewall(dx, dz * distance, yDelta: -2)); + moves.Add(MoveJump.Sidewall(dx * distance, dz, yDelta: -2)); + } + } + } + + // ---- dynamic-landing family (kept separate: variable landing depth) ---- + foreach (int dx in offsets) + { + moves.Add(new MoveDescend(dx, 0)); + moves.Add(new MoveSprintDescend(dx * 2, 0)); + moves.Add(new MoveSprintDescend(dx, dx)); + moves.Add(new MoveSprintDescend(dx, -dx)); + } + foreach (int dz in offsets) + { + moves.Add(new MoveDescend(0, dz)); + moves.Add(new MoveSprintDescend(0, dz * 2)); + } + + // ---- vertical / free fall ---- + moves.Add(new MoveClimb(true)); + moves.Add(new MoveClimb(false)); + moves.Add(new MoveFall()); + + return [.. moves]; + } + + public PathResult Calculate( + CalculationContext ctx, + int startX, int startY, int startZ, + IGoal goal, + CancellationToken ct, + long timeoutMs = 5000) + { + if (goal.IsInGoal(startX, startY, startZ)) + { + DebugLog?.Invoke($"[A*] Already in goal at ({startX},{startY},{startZ})"); + return new PathResult( + PathStatus.Success, + [new PathNode(startX, startY, startZ)], + nodesExplored: 0, + elapsedMs: 0); + } + + if (!IsGoalReachableFootPosition(ctx, goal)) + { + DebugLog?.Invoke($"[A*] Goal {goal} is not a reachable foot position"); + return PathResult.Fail(nodesExplored: 0, elapsedMs: 0); + } + + var sw = Stopwatch.StartNew(); + var openSet = new BinaryHeapOpenSet(4096); + var nodeMap = new Dictionary(4096); + + var startNode = new PathNode(startX, startY, startZ) + { + GCost = 0, + HCost = goal.Heuristic(startX, startY, startZ), + IsOpen = true + }; + openSet.Insert(startNode); + nodeMap[new NodeKey(startNode.PackedPosition, startNode.EntryPreparation)] = startNode; + + int nodesExplored = 0; + int unloadedChunkHits = 0; + bool searchAborted = false; + PathNode? bestPartialNode = startNode; + double bestPartialScore = startNode.HCost + startNode.GCost * 0.5; + + // Per-node scratch buffer for IMoveExpander output. Size = sum of + // MaxNeighbors across all expanders so no expander can overflow. + Span neighborBuffer = _totalExpanderCapacity <= 512 + ? stackalloc MoveNeighbor[_totalExpanderCapacity] + : new MoveNeighbor[_totalExpanderCapacity]; + + DebugLog?.Invoke($"[A*] Start ({startX},{startY},{startZ}), goal={goal}"); + + while (openSet.Count > 0) + { + if (ct.IsCancellationRequested) + { + searchAborted = true; + DebugLog?.Invoke($"[A*] Cancelled after {nodesExplored} nodes, {sw.ElapsedMilliseconds}ms"); + break; + } + + if (sw.ElapsedMilliseconds > timeoutMs) + { + searchAborted = true; + DebugLog?.Invoke($"[A*] Timeout ({timeoutMs}ms) after {nodesExplored} nodes"); + break; + } + + var current = openSet.RemoveMin(); + current.IsClosed = true; + nodesExplored++; + + if (goal.IsInGoal(current.X, current.Y, current.Z)) + { + DebugLog?.Invoke($"[A*] Goal reached! {nodesExplored} nodes, {sw.ElapsedMilliseconds}ms"); + var path = ReconstructPath(current); + return new PathResult(PathStatus.Success, path, nodesExplored, sw.ElapsedMilliseconds); + } + + ctx.PreviousMoveType = current.MoveUsed; + ctx.CurrentEntryPreparation = current.EntryPreparation; + + int bufferOffset = 0; + for (int ex = 0; ex < _expanders.Length; ex++) + { + IMoveExpander expander = _expanders[ex]; + Span slot = neighborBuffer.Slice(bufferOffset, expander.MaxNeighbors); + int produced = expander.Expand(ctx, current.X, current.Y, current.Z, slot); + bufferOffset += expander.MaxNeighbors; + + for (int i = 0; i < produced; i++) + { + MoveNeighbor emitted = slot[i]; + int nx = emitted.DestX; + int ny = emitted.DestY; + int nz = emitted.DestZ; + + if (!ctx.IsChunkLoaded(nx, nz)) + { + unloadedChunkHits++; + if (unloadedChunkHits > _maxChunkBorderFetch) + continue; + } + + double tentativeG = current.GCost + emitted.Cost; + EntryPreparationState nextPreparation = ResolveEntryPreparation( + current, emitted.MoveType, emitted.DestX, emitted.DestY, emitted.DestZ); + var key = new NodeKey(PathNode.Pack(nx, ny, nz), nextPreparation); + + if (nodeMap.TryGetValue(key, out var neighbor)) + { + if (neighbor.IsClosed) + continue; + if (tentativeG >= neighbor.GCost) + continue; + + neighbor.GCost = tentativeG; + neighbor.Parent = current; + neighbor.MoveUsed = emitted.MoveType; + neighbor.ParkourProfile = emitted.ParkourProfile; + neighbor.EntryPreparation = nextPreparation; + if (neighbor.IsOpen) + openSet.Update(neighbor); + } + else + { + neighbor = new PathNode(nx, ny, nz) + { + GCost = tentativeG, + HCost = goal.Heuristic(nx, ny, nz), + Parent = current, + MoveUsed = emitted.MoveType, + ParkourProfile = emitted.ParkourProfile, + EntryPreparation = nextPreparation, + IsOpen = true + }; + nodeMap[key] = neighbor; + openSet.Insert(neighbor); + } + + double partialScore = neighbor.HCost + neighbor.GCost * 0.5; + if (partialScore < bestPartialScore) + { + bestPartialScore = partialScore; + bestPartialNode = neighbor; + } + } + } + } + + if (bestPartialNode is not null + && bestPartialNode != startNode + && (searchAborted || unloadedChunkHits > 0)) + { + DebugLog?.Invoke($"[A*] Partial path to ({bestPartialNode.X},{bestPartialNode.Y},{bestPartialNode.Z}), " + + $"{nodesExplored} nodes, {sw.ElapsedMilliseconds}ms"); + var path = ReconstructPath(bestPartialNode); + return new PathResult(PathStatus.Partial, path, nodesExplored, sw.ElapsedMilliseconds); + } + + DebugLog?.Invoke($"[A*] Failed, {nodesExplored} nodes, {sw.ElapsedMilliseconds}ms"); + return PathResult.Fail(nodesExplored, sw.ElapsedMilliseconds); + } + + private EntryPreparationState ResolveEntryPreparation( + PathNode current, MoveType moveType, int destX, int destY, int destZ) + { + EntryPreparationState advanced = AdvanceExistingPreparation(current, moveType, destX, destY, destZ); + if (!advanced.IsNone) + return advanced; + + if (TryStartSidewallRunupPreparation(current, moveType, destX, destY, destZ, out EntryPreparationState started)) + return started; + + return EntryPreparationState.None; + } + + private static EntryPreparationState AdvanceExistingPreparation( + PathNode current, MoveType moveType, int destX, int destY, int destZ) + { + EntryPreparationState state = current.EntryPreparation; + if (state.IsNone) + return EntryPreparationState.None; + + if (moveType != MoveType.Traverse || destY != current.Y) + return EntryPreparationState.None; + + int stepX = destX - current.X; + int stepZ = destZ - current.Z; + + if (state.BackwardSteps < state.RequiredSteps + && stepX == -state.ForwardX + && stepZ == -state.ForwardZ) + { + return state.AdvanceBackward(); + } + + if (state.BackwardSteps == state.RequiredSteps + && state.ReturnSteps < state.RequiredSteps + && stepX == state.ForwardX + && stepZ == state.ForwardZ) + { + EntryPreparationState nextState = state.AdvanceReturn(); + if (nextState.IsPrepared + && (destX != state.OriginX + || destY != state.OriginY + || destZ != state.OriginZ)) + { + return EntryPreparationState.None; + } + + return nextState; + } + + return EntryPreparationState.None; + } + + private static bool TryStartSidewallRunupPreparation( + PathNode current, MoveType moveType, int destX, int destY, int destZ, out EntryPreparationState state) + { + state = EntryPreparationState.None; + + if (!current.EntryPreparation.IsNone + || moveType != MoveType.Traverse + || destY != current.Y) + { + return false; + } + + int stepX = destX - current.X; + int stepZ = destZ - current.Z; + + // Sidewall candidates are generated dynamically by JumpExpander's + // cardinal probe now, so we probe each cardinal forward direction + // directly instead of scanning a descriptor table. The only shape + // TryGetRequiredStaticEntryRunupSteps flags as needing a static + // runup today is (major=5, minor=1, yDelta=-1) -- we use that + // canonical shape as the query (lateral=+1 is arbitrary; the + // helper only looks at yDelta and major). + ReadOnlySpan<(int fx, int fz)> forwards = + [ + (1, 0), + (-1, 0), + (0, 1), + (0, -1), + ]; + + for (int i = 0; i < forwards.Length; i++) + { + (int forwardX, int forwardZ) = forwards[i]; + if (stepX != -forwardX || stepZ != -forwardZ) + continue; + + int xOffset, zOffset; + if (forwardX != 0) + { + xOffset = forwardX * 5; + zOffset = 1; + } + else + { + xOffset = 1; + zOffset = forwardZ * 5; + } + + if (!ParkourFeasibility.TryGetRequiredStaticEntryRunupSteps( + current.MoveUsed, + xOffset, + zOffset, + yDelta: -1, + out int requiredSteps)) + { + continue; + } + + state = new EntryPreparationState( + EntryPreparationKind.SidewallRunup, + current.X, + current.Y, + current.Z, + forwardX, + forwardZ, + (byte)requiredSteps, + BackwardSteps: 1, + ReturnSteps: 0); + return true; + } + + return false; + } + + private static List ReconstructPath(PathNode end) + { + var path = new List(); + var current = end; + while (current is not null) + { + path.Add(current); + current = current.Parent; + } + path.Reverse(); + return path; + } + + private static bool IsGoalReachableFootPosition(CalculationContext ctx, IGoal goal) + { + if (goal is not GoalBlock blockGoal) + return true; + + if (!ctx.IsChunkLoaded(blockGoal.X, blockGoal.Z)) + return true; + + if (blockGoal.Y == int.MinValue) + return false; + + return ctx.CanWalkOn(blockGoal.X, blockGoal.Y - 1, blockGoal.Z) + && ctx.CanWalkThrough(blockGoal.X, blockGoal.Y, blockGoal.Z) + && ctx.CanWalkThrough(blockGoal.X, blockGoal.Y + 1, blockGoal.Z); + } + } +} diff --git a/MinecraftClient/Pathing/Core/ActionCosts.cs b/MinecraftClient/Pathing/Core/ActionCosts.cs new file mode 100644 index 0000000000..544413fe62 --- /dev/null +++ b/MinecraftClient/Pathing/Core/ActionCosts.cs @@ -0,0 +1,63 @@ +namespace MinecraftClient.Pathing.Core +{ + /// + /// All pathfinding movement costs in ticks, derived from vanilla walking/sprinting speeds. + /// Mirrors Baritone's ActionCosts design. + /// + public static class ActionCosts + { + public const double WalkOneBlock = 20.0 / 4.317; + public const double SprintOneBlock = 20.0 / 5.612; + public const double SneakOneBlock = 20.0 / 1.3; + public const double LadderUpOne = 20.0 / 2.35; + public const double LadderDownOne = 20.0 / 3.0; + public const double WalkOffBlock = WalkOneBlock * 0.8; + public const double SprintMultiplier = SprintOneBlock / WalkOneBlock; + public const double DiagonalMultiplier = 1.4142135623730951; + public const double CostInf = 1_000_000; + + public const double JumpPenalty = 2.0; + + public static readonly double[] FallNBlocksCost = BuildFallTable(257); + + private static double[] BuildFallTable(int maxBlocks) + { + var table = new double[maxBlocks]; + table[0] = 0; + + double velocity = 0; + double distance = 0; + int ticks = 0; + int blockIndex = 1; + + while (blockIndex < maxBlocks) + { + velocity += 0.08; + velocity *= 0.98; + distance += velocity; + ticks++; + + while (blockIndex < maxBlocks && distance >= blockIndex) + { + table[blockIndex] = ticks; + blockIndex++; + } + + if (ticks > 10000) + break; + } + + for (int i = blockIndex; i < maxBlocks; i++) + table[i] = CostInf; + + return table; + } + + public static double FallCost(int blocks) + { + if (blocks < 0 || blocks >= FallNBlocksCost.Length) + return CostInf; + return FallNBlocksCost[blocks]; + } + } +} diff --git a/MinecraftClient/Pathing/Core/BinaryHeapOpenSet.cs b/MinecraftClient/Pathing/Core/BinaryHeapOpenSet.cs new file mode 100644 index 0000000000..2ea2cd6a71 --- /dev/null +++ b/MinecraftClient/Pathing/Core/BinaryHeapOpenSet.cs @@ -0,0 +1,96 @@ +using System; + +namespace MinecraftClient.Pathing.Core +{ + /// + /// Min-heap of PathNodes ordered by FCost, used as the A* open set. + /// + public sealed class BinaryHeapOpenSet + { + private PathNode[] _heap; + private int _size; + + public int Count => _size; + + public BinaryHeapOpenSet(int initialCapacity = 1024) + { + _heap = new PathNode[initialCapacity]; + _size = 0; + } + + public void Insert(PathNode node) + { + if (_size == _heap.Length) + Array.Resize(ref _heap, _heap.Length * 2); + + node.HeapIndex = _size; + _heap[_size] = node; + _size++; + SiftUp(_size - 1); + } + + public PathNode RemoveMin() + { + var min = _heap[0]; + _size--; + if (_size > 0) + { + _heap[0] = _heap[_size]; + _heap[0].HeapIndex = 0; + SiftDown(0); + } + _heap[_size] = null!; + min.IsOpen = false; + return min; + } + + public void Update(PathNode node) + { + SiftUp(node.HeapIndex); + } + + private void SiftUp(int i) + { + var node = _heap[i]; + while (i > 0) + { + int parent = (i - 1) >> 1; + if (Compare(node, _heap[parent]) >= 0) + break; + _heap[i] = _heap[parent]; + _heap[i].HeapIndex = i; + i = parent; + } + _heap[i] = node; + node.HeapIndex = i; + } + + private void SiftDown(int i) + { + var node = _heap[i]; + int half = _size >> 1; + while (i < half) + { + int left = (i << 1) + 1; + int right = left + 1; + int best = left; + if (right < _size && Compare(_heap[right], _heap[left]) < 0) + best = right; + if (Compare(node, _heap[best]) <= 0) + break; + _heap[i] = _heap[best]; + _heap[i].HeapIndex = i; + i = best; + } + _heap[i] = node; + node.HeapIndex = i; + } + + private static int Compare(PathNode a, PathNode b) + { + int cmp = a.FCost.CompareTo(b.FCost); + if (cmp != 0) return cmp; + return a.HCost.CompareTo(b.HCost); + } + } +} diff --git a/MinecraftClient/Pathing/Core/CalculationContext.cs b/MinecraftClient/Pathing/Core/CalculationContext.cs new file mode 100644 index 0000000000..181f83c3ad --- /dev/null +++ b/MinecraftClient/Pathing/Core/CalculationContext.cs @@ -0,0 +1,75 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Moves; + +namespace MinecraftClient.Pathing.Core +{ + /// + /// Thread-safe snapshot of world state and player capabilities for path planning. + /// Created once at the start of a search; all move calculations read from this. + /// + public sealed class CalculationContext + { + public World World { get; } + public bool CanSprint { get; } + public bool AllowParkour { get; } + public bool AllowParkourAscend { get; } + public bool AllowDiagonalDescend { get; } + public int MaxFallHeight { get; } + public int MaxFallHeightWater { get; } + public bool AllowLadderGrabDuringFall { get; } + public double JumpPenalty { get; } + public double WalkCost { get; } + public double SprintCost { get; } + public double SneakCost { get; } + public MoveType PreviousMoveType { get; internal set; } + public EntryPreparationState CurrentEntryPreparation { get; internal set; } + + public CalculationContext( + World world, + bool canSprint = true, + bool allowParkour = false, + bool allowParkourAscend = false, + bool allowDiagonalDescend = true, + int maxFallHeight = 3, + int maxFallHeightWater = 256, + bool allowLadderGrabDuringFall = true, + double jumpPenalty = ActionCosts.JumpPenalty) + { + World = world; + CanSprint = canSprint; + AllowParkour = allowParkour; + AllowParkourAscend = allowParkourAscend; + AllowDiagonalDescend = allowDiagonalDescend; + MaxFallHeight = maxFallHeight; + MaxFallHeightWater = maxFallHeightWater; + AllowLadderGrabDuringFall = allowLadderGrabDuringFall; + JumpPenalty = jumpPenalty; + WalkCost = ActionCosts.WalkOneBlock; + SprintCost = CanSprint ? ActionCosts.SprintOneBlock : ActionCosts.WalkOneBlock; + SneakCost = ActionCosts.SneakOneBlock; + } + + public Block GetBlock(int x, int y, int z) + => World.GetBlock(new Location(x, y, z)); + + public Material GetMaterial(int x, int y, int z) + => GetBlock(x, y, z).Type; + + public bool CanWalkThrough(int x, int y, int z) + => MoveHelper.CanWalkThrough(this, x, y, z); + + public bool CanWalkOn(int x, int y, int z) + => MoveHelper.CanWalkOn(this, x, y, z); + + public bool IsFullyPassable(int x, int y, int z) + => MoveHelper.IsFullyPassable(this, x, y, z); + + public bool IsChunkLoaded(int x, int z) + { + int cx = x >> 4; + int cz = z >> 4; + var col = World[cx, cz]; + return col is not null && col.FullyLoaded; + } + } +} diff --git a/MinecraftClient/Pathing/Core/EntryPreparationKind.cs b/MinecraftClient/Pathing/Core/EntryPreparationKind.cs new file mode 100644 index 0000000000..4ea73979d0 --- /dev/null +++ b/MinecraftClient/Pathing/Core/EntryPreparationKind.cs @@ -0,0 +1,8 @@ +namespace MinecraftClient.Pathing.Core +{ + public enum EntryPreparationKind + { + None = 0, + SidewallRunup = 1 + } +} diff --git a/MinecraftClient/Pathing/Core/EntryPreparationState.cs b/MinecraftClient/Pathing/Core/EntryPreparationState.cs new file mode 100644 index 0000000000..74ba33a51b --- /dev/null +++ b/MinecraftClient/Pathing/Core/EntryPreparationState.cs @@ -0,0 +1,29 @@ +namespace MinecraftClient.Pathing.Core +{ + public readonly record struct EntryPreparationState( + EntryPreparationKind Kind, + int OriginX, + int OriginY, + int OriginZ, + int ForwardX, + int ForwardZ, + byte RequiredSteps, + byte BackwardSteps, + byte ReturnSteps) + { + public static EntryPreparationState None => default; + + public bool IsNone => Kind == EntryPreparationKind.None; + + public bool IsPrepared => + Kind != EntryPreparationKind.None && + BackwardSteps == RequiredSteps && + ReturnSteps == RequiredSteps; + + public EntryPreparationState AdvanceBackward() => + this with { BackwardSteps = (byte)(BackwardSteps + 1) }; + + public EntryPreparationState AdvanceReturn() => + this with { ReturnSteps = (byte)(ReturnSteps + 1) }; + } +} diff --git a/MinecraftClient/Pathing/Core/MoveResult.cs b/MinecraftClient/Pathing/Core/MoveResult.cs new file mode 100644 index 0000000000..d1c71e584b --- /dev/null +++ b/MinecraftClient/Pathing/Core/MoveResult.cs @@ -0,0 +1,31 @@ +namespace MinecraftClient.Pathing.Core +{ + /// + /// Result of an IMove.Calculate() call. Mutable struct passed by ref for zero-alloc hot path. + /// + public struct MoveResult + { + public int DestX; + public int DestY; + public int DestZ; + public double Cost; + public ParkourProfile ParkourProfile; + + public void Set(int x, int y, int z, double cost, ParkourProfile parkourProfile = ParkourProfile.None) + { + DestX = x; + DestY = y; + DestZ = z; + Cost = cost; + ParkourProfile = parkourProfile; + } + + public void SetImpossible() + { + Cost = ActionCosts.CostInf; + ParkourProfile = ParkourProfile.None; + } + + public readonly bool IsImpossible => Cost >= ActionCosts.CostInf; + } +} diff --git a/MinecraftClient/Pathing/Core/MoveType.cs b/MinecraftClient/Pathing/Core/MoveType.cs new file mode 100644 index 0000000000..3d632012e0 --- /dev/null +++ b/MinecraftClient/Pathing/Core/MoveType.cs @@ -0,0 +1,13 @@ +namespace MinecraftClient.Pathing.Core +{ + public enum MoveType + { + Traverse, + Diagonal, + Ascend, + Descend, + Fall, + Climb, + Parkour + } +} diff --git a/MinecraftClient/Pathing/Core/ParkourProfile.cs b/MinecraftClient/Pathing/Core/ParkourProfile.cs new file mode 100644 index 0000000000..990ea68b6e --- /dev/null +++ b/MinecraftClient/Pathing/Core/ParkourProfile.cs @@ -0,0 +1,9 @@ +namespace MinecraftClient.Pathing.Core +{ + public enum ParkourProfile + { + None = 0, + Default = 1, + Sidewall = 2 + } +} diff --git a/MinecraftClient/Pathing/Core/PathNode.cs b/MinecraftClient/Pathing/Core/PathNode.cs new file mode 100644 index 0000000000..5efd80fceb --- /dev/null +++ b/MinecraftClient/Pathing/Core/PathNode.cs @@ -0,0 +1,43 @@ +namespace MinecraftClient.Pathing.Core +{ + /// + /// A* search node. Stored in the open/closed sets during pathfinding. + /// + public sealed class PathNode + { + public readonly int X; + public readonly int Y; + public readonly int Z; + + public double GCost; + public double HCost; + public double FCost => GCost + HCost; + + public PathNode? Parent; + public MoveType MoveUsed; + public ParkourProfile ParkourProfile; + public EntryPreparationState EntryPreparation; + + public int HeapIndex; + public bool IsOpen; + public bool IsClosed; + + public PathNode(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } + + public long PackedPosition => Pack(X, Y, Z); + + public static long Pack(int x, int y, int z) + { + // 26 bits for X (0..60M), 26 bits for Z (0..60M), 12 bits for Y (-2048..2047) + long px = (long)(x + 30_000_000) & 0x3FFFFFF; + long pz = (long)(z + 30_000_000) & 0x3FFFFFF; + long py = (long)(y + 2048) & 0xFFF; + return (px << 38) | (pz << 12) | py; + } + } +} diff --git a/MinecraftClient/Pathing/Core/PathResult.cs b/MinecraftClient/Pathing/Core/PathResult.cs new file mode 100644 index 0000000000..9d9ba7eecc --- /dev/null +++ b/MinecraftClient/Pathing/Core/PathResult.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace MinecraftClient.Pathing.Core +{ + public enum PathStatus + { + Success, + Partial, + Failed + } + + public sealed class PathResult + { + public PathStatus Status { get; } + public IReadOnlyList Path { get; } + public int NodesExplored { get; } + public long ElapsedMs { get; } + + public PathResult(PathStatus status, IReadOnlyList path, int nodesExplored, long elapsedMs) + { + Status = status; + Path = path; + NodesExplored = nodesExplored; + ElapsedMs = elapsedMs; + } + + public static PathResult Fail(int nodesExplored, long elapsedMs) + => new(PathStatus.Failed, [], nodesExplored, elapsedMs); + } +} diff --git a/MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs b/MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs new file mode 100644 index 0000000000..2e20490c3e --- /dev/null +++ b/MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs @@ -0,0 +1,27 @@ +using System; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution.Templates; + +namespace MinecraftClient.Pathing.Execution +{ + /// + /// Maps a PathSegment (MoveType + start/end) to the appropriate IActionTemplate. + /// + public static class ActionTemplateFactory + { + public static IActionTemplate Create(PathSegment segment, PathSegment? nextSegment) + { + return segment.MoveType switch + { + MoveType.Traverse => new WalkTemplate(segment, nextSegment), + MoveType.Diagonal => new WalkTemplate(segment, nextSegment), + MoveType.Ascend => new AscendTemplate(segment, nextSegment), + MoveType.Descend => new DescendTemplate(segment, nextSegment), + MoveType.Fall => new FallTemplate(segment, nextSegment), + MoveType.Climb => new ClimbTemplate(segment, nextSegment), + MoveType.Parkour => new SprintJumpTemplate(segment, nextSegment), + _ => throw new ArgumentException($"Unknown MoveType: {segment.MoveType}") + }; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/IActionTemplate.cs b/MinecraftClient/Pathing/Execution/IActionTemplate.cs new file mode 100644 index 0000000000..6e79278046 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/IActionTemplate.cs @@ -0,0 +1,25 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public enum TemplateState + { + InProgress, + Complete, + Failed + } + + /// + /// Per-tick movement controller for one path segment. + /// Reads player state from physics, writes desired input to MovementInput, + /// and reports completion or failure. + /// + public interface IActionTemplate + { + Location ExpectedStart { get; } + Location ExpectedEnd { get; } + + TemplateState Tick(Location currentPos, PlayerPhysics physics, MovementInput input, World world); + } +} diff --git a/MinecraftClient/Pathing/Execution/PathExecutor.cs b/MinecraftClient/Pathing/Execution/PathExecutor.cs new file mode 100644 index 0000000000..567dbc49e4 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathExecutor.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Telemetry; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public enum PathExecutorState + { + InProgress, + Failed, + Complete + } + + /// + /// Drives a sequence of PathSegments by instantiating the correct IActionTemplate + /// for each segment and ticking it every game tick. + /// + public sealed class PathExecutor + { + private readonly List _segments; + private int _currentIndex; + private IActionTemplate? _currentTemplate; + private readonly Action? _debugLog; + private readonly IPathExecutionObserver? _observer; + private int _segmentTicks; + private int _totalTicks; + + public bool IsComplete => _currentIndex >= _segments.Count && _currentTemplate is null; + public int CurrentIndex => _currentIndex; + public int TotalSegments => _segments.Count; + public int TotalTicks => _totalTicks; + public PathSegment? CurrentSegment => + _currentIndex < _segments.Count ? _segments[_currentIndex] : null; + public PathSegment? LastSegment => + _segments.Count > 0 ? _segments[^1] : null; + + public PathExecutor(List segments, Action? debugLog = null, IPathExecutionObserver? observer = null) + { + _segments = segments; + _currentIndex = 0; + _debugLog = debugLog; + _observer = observer; + _observer?.OnNavigationStarted(segments); + AdvanceToNextSegment(); + } + + public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (_currentTemplate is null) + { + input.Reset(); + return PathExecutorState.Complete; + } + + _totalTicks++; + int sameTickAdvanceCount = 0; + while (_currentTemplate is not null) + { + _segmentTicks++; + var state = _currentTemplate.Tick(pos, physics, input, world); + + switch (state) + { + case TemplateState.Complete: + _observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})"); + _currentIndex++; + _segmentTicks = 0; + if (_currentIndex >= _segments.Count) + { + input.Reset(); + _currentTemplate = null; + _debugLog?.Invoke("[PathExec] All segments complete!"); + return PathExecutorState.Complete; + } + + AdvanceToNextSegment(); + + // Do not waste the handoff tick when the next segment needs to issue + // a jump or braking input immediately. + sameTickAdvanceCount++; + if (sameTickAdvanceCount > _segments.Count) + { + input.Reset(); + _debugLog?.Invoke("[PathExec] Excessive same-tick segment advances; aborting."); + return PathExecutorState.Failed; + } + continue; + + case TemplateState.Failed: + input.Reset(); + _observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " + + $"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})"); + return PathExecutorState.Failed; + + default: + return PathExecutorState.InProgress; + } + } + + input.Reset(); + return PathExecutorState.Complete; + } + + private void AdvanceToNextSegment() + { + if (_currentIndex < _segments.Count) + { + var seg = _segments[_currentIndex]; + PathSegment? next = _currentIndex + 1 < _segments.Count ? _segments[_currentIndex + 1] : null; + _currentTemplate = ActionTemplateFactory.Create(seg, next); + _observer?.OnSegmentStarted(_currentIndex, _segments.Count, seg); + _debugLog?.Invoke($"[PathExec] Starting segment {_currentIndex}/{_segments.Count}: {seg}"); + } + else + { + _currentTemplate = null; + } + } + } +} diff --git a/MinecraftClient/Pathing/Execution/PathSegment.cs b/MinecraftClient/Pathing/Execution/PathSegment.cs new file mode 100644 index 0000000000..1eebceae64 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathSegment.cs @@ -0,0 +1,23 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Execution +{ + public sealed class PathSegment + { + public required Location Start { get; init; } + public required Location End { get; init; } + public required MoveType MoveType { get; init; } + public ParkourProfile ParkourProfile { get; init; } = ParkourProfile.None; + public PathTransitionType ExitTransition { get; init; } = PathTransitionType.FinalStop; + public PathTransitionHints ExitHints { get; init; } = PathTransitionHints.Default; + public bool PreserveSprint { get; init; } + + public int HeadingX => Math.Sign(End.X - Start.X); + public int HeadingZ => Math.Sign(End.Z - Start.Z); + + public override string ToString() => + $"{MoveType}: ({Start.X:F1},{Start.Y:F1},{Start.Z:F1})->({End.X:F1},{End.Y:F1},{End.Z:F1}), transition={ExitTransition}, preserveSprint={PreserveSprint}"; + } +} diff --git a/MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs b/MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs new file mode 100644 index 0000000000..5e655a9959 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Execution +{ + public static class PathSegmentBuilder + { + public static List FromPath(IReadOnlyList nodes) + { + var segments = new List(Math.Max(0, nodes.Count - 1)); + for (int i = 1; i < nodes.Count; i++) + { + PathSegment current = CreatePreview(nodes[i - 1], nodes[i]); + PathSegment? next = i + 1 < nodes.Count ? CreatePreview(nodes[i], nodes[i + 1]) : null; + PathSegment? nextNext = i + 2 < nodes.Count ? CreatePreview(nodes[i + 1], nodes[i + 2]) : null; + + PathTransitionType exitTransition = Classify(current, next); + segments.Add(new PathSegment + { + Start = current.Start, + End = current.End, + MoveType = current.MoveType, + ParkourProfile = current.ParkourProfile, + ExitTransition = exitTransition, + ExitHints = BuildHints(current, next, nextNext, exitTransition), + PreserveSprint = exitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump + }); + } + return segments; + } + + private static PathTransitionType Classify(PathSegment current, PathSegment? next) + { + if (next is null) + return PathTransitionType.FinalStop; + + if (next.MoveType is MoveType.Parkour or MoveType.Ascend) + return PathTransitionType.PrepareJump; + + if (current.MoveType is MoveType.Parkour or MoveType.Descend or MoveType.Fall) + return PathTransitionType.LandingRecovery; + + if (current.HeadingX == next.HeadingX && current.HeadingZ == next.HeadingZ) + return PathTransitionType.ContinueStraight; + + return PathTransitionType.Turn; + } + + private static PathSegment CreatePreview(PathNode start, PathNode end) + { + return new PathSegment + { + Start = new Location(start.X + 0.5, start.Y, start.Z + 0.5), + End = new Location(end.X + 0.5, end.Y, end.Z + 0.5), + MoveType = end.MoveUsed, + ParkourProfile = end.ParkourProfile + }; + } + + private static PathTransitionHints BuildHints(PathSegment current, PathSegment? next, PathSegment? nextNext, + PathTransitionType exitTransition) + { + if (next is null) + { + return new PathTransitionHints( + DesiredHeadingX: current.HeadingX, + DesiredHeadingZ: current.HeadingZ, + MinExitSpeed: 0.0, + MaxExitSpeed: 0.02, + RequireStableFooting: true, + RequireGrounded: true, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 12); + } + + if (next.MoveType is MoveType.Parkour or MoveType.Ascend) + { + return new PathTransitionHints( + DesiredHeadingX: next.HeadingX, + DesiredHeadingZ: next.HeadingZ, + MinExitSpeed: next.MoveType == MoveType.Parkour ? 0.10 : 0.0, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: true, + RequireJumpReady: true, + AllowAirBrake: false, + HorizonTicks: 10); + } + + bool turning = current.HeadingX != next.HeadingX || current.HeadingZ != next.HeadingZ; + bool nextImmediatelyJumps = nextNext is not null + && nextNext.MoveType is (MoveType.Parkour or MoveType.Ascend); + + // LandingRecovery (current is Descend/Parkour/Fall) takes precedence + // over the turning branch even when heading changes between current + // and next. The turning branch demands RequireStableFooting=true, + // which forces the GroundedSegmentController completion gate to wait + // for IsSettledOnTargetBlock (footprint inside, won't leave next + // tick, horizontal speed^2 <= 0.0016). After a multi-block diagonal + // Descend the bot lands inside the target block already carrying + // ~0.02 m/tick of residual momentum that the planner can't shed + // cleanly: the per-tick yaw bias toward the next segment's heading + // pulls the bot off-axis, the bot slides off the target block, the + // template re-targets the centre, and so on for ~60 ticks until + // momentum decays. The LandingRecovery shortcut in + // GroundedSegmentController.ShouldComplete (!RequireStableFooting + + // footprint inside target) bypasses the speed gate cleanly the + // moment the bot reaches the landing column. + if (exitTransition == PathTransitionType.LandingRecovery) + { + return new PathTransitionHints( + DesiredHeadingX: next.HeadingX, + DesiredHeadingZ: next.HeadingZ, + MinExitSpeed: 0.03, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: true, + RequireJumpReady: false, + AllowAirBrake: true, + HorizonTicks: 12); + } + + if (turning) + { + return new PathTransitionHints( + DesiredHeadingX: next.HeadingX, + DesiredHeadingZ: next.HeadingZ, + MinExitSpeed: nextImmediatelyJumps ? 0.05 : 0.0, + MaxExitSpeed: nextImmediatelyJumps ? 0.16 : 0.05, + RequireStableFooting: !nextImmediatelyJumps, + RequireGrounded: true, + RequireJumpReady: nextImmediatelyJumps, + AllowAirBrake: true, + HorizonTicks: 12); + } + + return new PathTransitionHints( + DesiredHeadingX: next.HeadingX, + DesiredHeadingZ: next.HeadingZ, + MinExitSpeed: 0.06, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: false, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 8); + } + } +} diff --git a/MinecraftClient/Pathing/Execution/PathSegmentManager.cs b/MinecraftClient/Pathing/Execution/PathSegmentManager.cs new file mode 100644 index 0000000000..d64052d418 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathSegmentManager.cs @@ -0,0 +1,624 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution.Telemetry; +using MinecraftClient.Pathing.Goals; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + /// + /// Top-level navigation controller. Holds a PathExecutor, monitors its progress, + /// and triggers replanning on failure or deviation. + /// + /// Replans and look-ahead plans run on a background Task (Baritone equivalent: + /// findPathInNewThread). The main tick only reads task state: either applies a + /// completed plan or installs the next one. A look-ahead plan is speculatively + /// started as the current executor nears the end of its segment list so that the + /// next executor is ready to splice in without a user-visible pause. + /// + public sealed class PathSegmentManager + { + private const int MaxReplans = 5; + private const int ReplanTimeoutMs = 3000; + private const int LookaheadTriggerSegmentsRemaining = 2; + + private readonly Action? _debugLog; + private readonly Action? _infoLog; + private readonly IPathExecutionObserver? _observer; + + private PathExecutor? _executor; + private PathExecutor? _nextExecutor; + private IGoal? _goal; + private int _replanCount; + private bool _isInitialPlan; + + private Task? _pendingReplan; + private CancellationTokenSource? _pendingReplanCts; + private Task? _pendingLookahead; + private CancellationTokenSource? _pendingLookaheadCts; + private (int x, int y, int z)? _pendingLookaheadAnchor; + + /// + /// Set to true to emit Info-level diagnostic traces (full path node dump, + /// failing-segment context, recent position tail) on every plan/replan + /// event. Users enable this via /pathdiag on when reporting pathing + /// bugs so the default log level stays quiet. + /// + public static bool DiagnosticsEnabled { get; set; } + + private const int DiagnosticsTailSize = 200; + private const int SlowSegmentDumpTickThreshold = 25; + private readonly Queue _diagnosticsTail = new(DiagnosticsTailSize + 1); + private PathResult? _lastPlan; + private int _lastObservedSegmentIndex = -1; + private int _ticksSinceSegmentStart; + + // Diagnostic emission runs on the thread pool to keep the 20 TPS tick + // unblocked. Each batch is appended to a single chained Task so that + // line ordering is preserved across batches, even when the same tick + // produces both a slow-segment dump and the next seg-> header. Without + // the chain, multiple Task.Run calls could interleave and mangle the + // log output. + // + // Without this offload, a 200-line failure dump issued synchronously + // through ConsoleIO.WriteLogLine -> file logger took 200-500 ms on + // the main tick. The stalled tick stops position packets so the + // server view freezes, then snaps forward when the tick resumes - + // exactly the "freeze, jump, freeze" the user reported when /pathdiag + // was on. + private Task _diagFlushTail = Task.CompletedTask; + private readonly object _diagFlushLock = new(); + + public bool IsNavigating => + (_executor is not null && !_executor.IsComplete) + || _nextExecutor is not null + || _pendingReplan is not null; + + public int ReplanCount => _replanCount; + public IGoal? Goal => _goal; + + public PathSegmentManager(Action? debugLog = null, Action? infoLog = null, IPathExecutionObserver? observer = null) + { + _debugLog = debugLog; + _infoLog = infoLog; + _observer = observer; + } + + public void StartNavigation(IGoal goal, PathResult result) + { + CancelPendingTasks(); + _nextExecutor = null; + _goal = goal; + _replanCount = 0; + _isInitialPlan = false; + if (result.Status == PathStatus.Failed || result.Path.Count < 2) + { + _infoLog?.Invoke("[PathMgr] Navigation rejected -- no path found."); + _executor = null; + _goal = null; + return; + } + + var segments = PathSegmentBuilder.FromPath(result.Path); + _executor = new PathExecutor(segments, _debugLog, _observer); + _lastObservedSegmentIndex = -1; + _ticksSinceSegmentStart = 0; + _infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments"); + } + + /// + /// Kicks off the initial A* search on a background task and returns + /// immediately. The main tick loop drains the task via + /// , installing the executor when the + /// plan completes. Use this from interactive entry points (e.g. + /// /goto) so the 20 TPS tick loop never blocks on a long A* + /// search -- a complex climb can take the full planning budget + /// (several seconds) and freezing the tick causes the player to + /// desync, stop sending keep-alives, and miss chunk updates. + /// + public void StartNavigationAsync(IGoal goal, Location startPos, World world, long timeoutMs) + { + ArgumentNullException.ThrowIfNull(goal); + ArgumentNullException.ThrowIfNull(world); + + CancelPendingTasks(); + _executor = null; + _nextExecutor = null; + _goal = goal; + _replanCount = 0; + _isInitialPlan = true; + + int sx = (int)Math.Floor(startPos.X); + int sy = (int)Math.Floor(startPos.Y); + int sz = (int)Math.Floor(startPos.Z); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + if (!ctx.CanWalkThrough(sx, sy, sz) && ctx.CanWalkThrough(sx, sy + 1, sz)) + sy++; + + _debugLog?.Invoke($"[PathMgr] Initial plan kicked off from ({sx},{sy},{sz}) to {goal}"); + + _pendingReplanCts = new CancellationTokenSource(); + CancellationToken token = _pendingReplanCts.Token; + Action? debugLog = _debugLog; + long budget = timeoutMs; + _pendingReplan = Task.Run(() => + { + var finder = new AStarPathFinder { DebugLog = debugLog }; + return finder.Calculate(ctx, sx, sy, sz, goal, token, budget); + }, token); + } + + public void Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + DrainPendingReplan(pos, world); + DrainPendingLookahead(); + + if (_executor is null) + { + // Navigation is still alive if we are waiting on a background plan to + // come back. The next tick will promote it into _executor. + if (_pendingReplan is not null || _nextExecutor is not null) + { + TryPromoteNextExecutor(); + input.Reset(); + return; + } + + return; + } + + if (DiagnosticsEnabled) + RecordDiagnosticsSample(pos, physics); + + var state = _executor.Tick(pos, physics, input, world); + + switch (state) + { + case PathExecutorState.Complete: + HandleExecutorComplete(pos, world, input); + break; + + case PathExecutorState.Failed: + if (DiagnosticsEnabled) + EmitSegmentFailureDiagnostics(pos); + _infoLog?.Invoke("[PathMgr] Segment failed, replanning..."); + // The prepared next path assumes we finished the current segment + // cleanly, so drop it when we fail. + DiscardLookahead(); + _nextExecutor = null; + StartReplanAsync(pos, world); + break; + + case PathExecutorState.InProgress: + MaybeStartLookahead(world); + break; + } + } + + private void RecordDiagnosticsSample(Location pos, PlayerPhysics physics) + { + if (_executor is null) + return; + int segIdx = _executor.CurrentIndex; + PathSegment? seg = _executor.CurrentSegment; + string segStr = seg is null + ? "none" + : $"seg{segIdx}/{_executor.TotalSegments} {seg.MoveType} ({seg.Start.X:F1},{seg.Start.Y:F1},{seg.Start.Z:F1})->({seg.End.X:F1},{seg.End.Y:F1},{seg.End.Z:F1})"; + + // Emit an Info-level transition event whenever the executor steps to a + // new segment so the caller can reconstruct the full execution timeline + // without relying on the bounded tail buffer. Resets on plan install. + if (segIdx != _lastObservedSegmentIndex) + { + if (_lastObservedSegmentIndex >= 0 + && _ticksSinceSegmentStart >= SlowSegmentDumpTickThreshold) + { + int toDump = Math.Min(_diagnosticsTail.Count, _ticksSinceSegmentStart); + int skipCount = _diagnosticsTail.Count - toDump; + var batch = new List(toDump + 1) + { + $"[PathDiag] Slow segment {_lastObservedSegmentIndex}/{_executor.TotalSegments} took {_ticksSinceSegmentStart} ticks, dumping last {toDump} ticks:" + }; + int i = 0; + foreach (string line in _diagnosticsTail) + { + if (i++ < skipCount) + continue; + batch.Add($"[PathDiag] t-{toDump - (i - skipCount)}: {line}"); + } + DispatchDiagnosticsBatch(batch); + } + + _lastObservedSegmentIndex = segIdx; + _ticksSinceSegmentStart = 0; + DispatchDiagnosticsBatch(new[] + { + $"[PathDiag] seg->{segIdx}/{_executor.TotalSegments} pos=({pos.X:F2},{pos.Y:F2},{pos.Z:F2}) yaw={physics.Yaw:F1} vy={physics.DeltaMovement.Y:F3} og={physics.OnGround} " + + (seg is null ? "none" : $"{seg.MoveType} ({seg.Start.X:F1},{seg.Start.Y:F1},{seg.Start.Z:F1})->({seg.End.X:F1},{seg.End.Y:F1},{seg.End.Z:F1}) exit={seg.ExitTransition}") + }); + } + else + { + _ticksSinceSegmentStart++; + } + + _diagnosticsTail.Enqueue( + $"pos=({pos.X:F2},{pos.Y:F2},{pos.Z:F2}) yaw={physics.Yaw:F1} vy={physics.DeltaMovement.Y:F3} vx={physics.DeltaMovement.X:F3} vz={physics.DeltaMovement.Z:F3} og={physics.OnGround} {segStr}"); + while (_diagnosticsTail.Count > DiagnosticsTailSize) + _diagnosticsTail.Dequeue(); + } + + private void EmitSegmentFailureDiagnostics(Location pos) + { + if (_executor is null) + return; + PathSegment? seg = _executor.CurrentSegment; + int segIdx = _executor.CurrentIndex; + var batch = new List(_diagnosticsTail.Count + 2) + { + $"[PathDiag] Failure context: pos=({pos.X:F2},{pos.Y:F2},{pos.Z:F2}) failingSeg={segIdx}/{_executor.TotalSegments} " + + (seg is null ? "seg=" : $"seg={seg.MoveType} ({seg.Start.X:F1},{seg.Start.Y:F1},{seg.Start.Z:F1})->({seg.End.X:F1},{seg.End.Y:F1},{seg.End.Z:F1}) exit={seg.ExitTransition}") + }; + if (_diagnosticsTail.Count > 0) + { + batch.Add($"[PathDiag] Recent tick trace (last {_diagnosticsTail.Count}):"); + int i = 0; + int total = _diagnosticsTail.Count; + foreach (string line in _diagnosticsTail) + batch.Add($"[PathDiag] t-{total - i++ - 1}: {line}"); + } + DispatchDiagnosticsBatch(batch); + } + + private void EmitPathDumpDiagnostics(string label, PathResult result, int startIdx = 0) + { + if (!DiagnosticsEnabled) + return; + int count = result.Path.Count; + var batch = new List(count + 1) + { + $"[PathDiag] {label}: {count} waypoints, status={result.Status}, nodes={result.NodesExplored}, time={result.ElapsedMs}ms" + }; + for (int i = 0; i < count; i++) + { + var node = result.Path[i]; + string move = i == 0 ? "Start" : node.MoveUsed.ToString(); + batch.Add($"[PathDiag] [{startIdx + i:D2}] {move,-22} ({node.X},{node.Y},{node.Z})"); + } + DispatchDiagnosticsBatch(batch); + } + + /// + /// Schedule a diagnostics line batch for emission on a background task, + /// chained behind any prior batch so output order is preserved. The + /// caller's snapshot is captured by reference; the input list MUST not + /// be mutated after dispatch. + /// + private void DispatchDiagnosticsBatch(IReadOnlyList lines) + { + Action? infoLog = _infoLog; + if (infoLog is null || lines.Count == 0) + return; + + lock (_diagFlushLock) + { + _diagFlushTail = _diagFlushTail.ContinueWith(_ => + { + for (int i = 0; i < lines.Count; i++) + { + try + { + infoLog(lines[i]); + } + catch + { + // Swallow logger faults so a downstream sink failure + // never tears down the chain (which would silently + // drop every subsequent diagnostics batch). + } + } + }, TaskScheduler.Default); + } + } + + public void Cancel() + { + if (_executor is not null || _pendingReplan is not null || _nextExecutor is not null) + { + _infoLog?.Invoke("[PathMgr] Navigation cancelled."); + } + + CancelPendingTasks(); + _executor = null; + _nextExecutor = null; + _goal = null; + } + + private void HandleExecutorComplete(Location pos, World world, MovementInput input) + { + if (_goal is not null) + { + int px = (int)Math.Floor(pos.X); + int py = (int)Math.Floor(pos.Y); + int pz = (int)Math.Floor(pos.Z); + if (!_goal.IsInGoal(px, py, pz)) + { + if (TryPromoteNextExecutor()) + { + _debugLog?.Invoke("[PathMgr] Spliced to prepared next segment chain."); + return; + } + + _infoLog?.Invoke("[PathMgr] Planned route ended before reaching goal, replanning..."); + StartReplanAsync(pos, world); + input.Reset(); + return; + } + } + + _observer?.OnNavigationCompleted(_executor!.TotalTicks); + _infoLog?.Invoke("[PathMgr] Navigation complete!"); + CancelPendingTasks(); + _executor = null; + _nextExecutor = null; + _goal = null; + } + + private bool TryPromoteNextExecutor() + { + if (_nextExecutor is null) + return false; + + _executor = _nextExecutor; + _nextExecutor = null; + return true; + } + + private void StartReplanAsync(Location pos, World world) + { + _replanCount++; + _observer?.OnReplanStarted(_replanCount, pos); + if (_replanCount > MaxReplans) + { + _observer?.OnReplanFailed(_replanCount, pos); + _infoLog?.Invoke($"[PathMgr] Giving up after {MaxReplans} replans."); + CancelPendingTasks(); + _executor = null; + _nextExecutor = null; + _goal = null; + return; + } + + if (_goal is null) + { + _executor = null; + return; + } + + // Once we decide to replan, the currently-installed executor is discarded; + // the new plan will replace it. We keep the executor reference until the + // plan returns so IsNavigating reflects that work is in flight. + if (_pendingReplan is not null) + return; + + int sx = (int)Math.Floor(pos.X); + int sy = (int)Math.Floor(pos.Y); + int sz = (int)Math.Floor(pos.Z); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + if (!ctx.CanWalkThrough(sx, sy, sz) && ctx.CanWalkThrough(sx, sy + 1, sz)) + sy++; + + IGoal goal = _goal; + _debugLog?.Invoke($"[PathMgr] Replan #{_replanCount} kicked off from ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})"); + + _pendingReplanCts = new CancellationTokenSource(); + CancellationToken token = _pendingReplanCts.Token; + _pendingReplan = Task.Run(() => + { + var finder = new AStarPathFinder { DebugLog = _debugLog }; + return finder.Calculate(ctx, sx, sy, sz, goal, token, ReplanTimeoutMs); + }, token); + + // Clear the current executor so IsNavigating stays true via the pending + // task branch. This prevents the main client from believing navigation + // ended between "plan completes" and "plan gets installed". + _executor = null; + } + + private void DrainPendingReplan(Location pos, World world) + { + if (_pendingReplan is null) + return; + + // When the current executor has been cleared (we are waiting for a plan + // to install), give the background task a short budget to finish. The + // player is standing still anyway, so trading a few ms of pause for + // installing the new plan immediately is a clear win over letting the + // tick return with _executor == null. This also makes tests with a + // tight Tick poll loop deterministic: Task.Run needs the caller to + // yield at some point so the thread-pool worker can complete. + if (!_pendingReplan.IsCompleted && _executor is null && _nextExecutor is null) + { + try + { + _pendingReplan.Wait(20); + } + catch + { + // Exceptions are inspected via Task.IsFaulted below. + } + } + + if (!_pendingReplan.IsCompleted) + return; + + Task task = _pendingReplan; + _pendingReplan = null; + var cts = _pendingReplanCts; + _pendingReplanCts = null; + cts?.Dispose(); + + bool isInitial = _isInitialPlan; + _isInitialPlan = false; + + if (task.IsFaulted || task.IsCanceled) + { + _infoLog?.Invoke(isInitial + ? "[PathMgr] Initial plan failed or was cancelled." + : "[PathMgr] Replan task failed or was cancelled."); + if (!isInitial) + _observer?.OnReplanFailed(_replanCount, pos); + _goal = null; + _executor = null; + _nextExecutor = null; + return; + } + + PathResult result = task.Result; + + int sx = (int)Math.Floor(pos.X); + int sy = (int)Math.Floor(pos.Y); + int sz = (int)Math.Floor(pos.Z); + bool alreadyInGoal = _goal is not null && (_goal.IsInGoal(sx, sy, sz) + || (result.Path.Count == 1 && _goal.IsInGoal(result.Path[0].X, result.Path[0].Y, result.Path[0].Z))); + + if (alreadyInGoal) + { + _infoLog?.Invoke("[PathMgr] Navigation complete!"); + _executor = null; + _nextExecutor = null; + _goal = null; + return; + } + + if (result.Status == PathStatus.Failed || result.Path.Count < 2) + { + if (!isInitial) + _observer?.OnReplanFailed(_replanCount, pos); + _infoLog?.Invoke(isInitial + ? $"[PathMgr] No path found (nodes={result.NodesExplored}, time={result.ElapsedMs}ms)." + : "[PathMgr] Replan failed -- no path found."); + _executor = null; + _nextExecutor = null; + _goal = null; + return; + } + + var segments = PathSegmentBuilder.FromPath(result.Path); + _lastPlan = result; + _diagnosticsTail.Clear(); + _lastObservedSegmentIndex = -1; + _ticksSinceSegmentStart = 0; + if (isInitial) + { + _executor = new PathExecutor(segments, _debugLog, _observer); + _nextExecutor = null; + string partial = result.Status == PathStatus.Partial ? " (partial)" : ""; + _infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments, nodes={result.NodesExplored}, time={result.ElapsedMs}ms{partial}"); + EmitPathDumpDiagnostics("Initial plan", result); + } + else + { + _observer?.OnReplanSucceeded(_replanCount, segments); + _executor = new PathExecutor(segments, _debugLog, _observer); + _nextExecutor = null; + _infoLog?.Invoke($"[PathMgr] Replanned: {segments.Count} segments (replan #{_replanCount})"); + EmitPathDumpDiagnostics($"Replan #{_replanCount} plan", result); + } + } + + private void MaybeStartLookahead(World world) + { + if (_pendingLookahead is not null || _nextExecutor is not null || _goal is null || _executor is null) + return; + + int total = _executor.TotalSegments; + int current = _executor.CurrentIndex; + if (total - current > LookaheadTriggerSegmentsRemaining) + return; + + // Anchor the lookahead at the final segment's end; that is where execution + // will arrive if the current executor finishes without drift. + PathSegment? lastSegment = _executor.LastSegment; + if (lastSegment is null) + return; + + int ax = (int)Math.Floor(lastSegment.End.X); + int ay = (int)Math.Floor(lastSegment.End.Y); + int az = (int)Math.Floor(lastSegment.End.Z); + + if (_goal.IsInGoal(ax, ay, az)) + return; + + // Avoid re-planning from the same anchor repeatedly. + if (_pendingLookaheadAnchor is { } prev && prev == (ax, ay, az)) + return; + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + IGoal goal = _goal; + _debugLog?.Invoke($"[PathMgr] Lookahead plan kicked off from ({ax},{ay},{az})"); + + _pendingLookaheadCts = new CancellationTokenSource(); + CancellationToken token = _pendingLookaheadCts.Token; + _pendingLookaheadAnchor = (ax, ay, az); + _pendingLookahead = Task.Run(() => + { + var finder = new AStarPathFinder { DebugLog = _debugLog }; + return finder.Calculate(ctx, ax, ay, az, goal, token, ReplanTimeoutMs); + }, token); + } + + private void DrainPendingLookahead() + { + if (_pendingLookahead is null || !_pendingLookahead.IsCompleted) + return; + + Task task = _pendingLookahead; + _pendingLookahead = null; + var cts = _pendingLookaheadCts; + _pendingLookaheadCts = null; + cts?.Dispose(); + + if (task.IsFaulted || task.IsCanceled) + return; + + PathResult result = task.Result; + if (result.Status == PathStatus.Failed || result.Path.Count < 2) + return; + + // The lookahead should continue from the anchor point. If the live + // executor is still running, splice the prepared plan as _nextExecutor + // so it can take over without a wait. + var segments = PathSegmentBuilder.FromPath(result.Path); + _nextExecutor = new PathExecutor(segments, _debugLog, _observer); + _debugLog?.Invoke($"[PathMgr] Lookahead ready: {segments.Count} segments spliced into _nextExecutor"); + } + + private void DiscardLookahead() + { + _pendingLookaheadCts?.Cancel(); + _pendingLookaheadCts?.Dispose(); + _pendingLookaheadCts = null; + _pendingLookahead = null; + _pendingLookaheadAnchor = null; + } + + private void CancelPendingTasks() + { + _pendingReplanCts?.Cancel(); + _pendingReplanCts?.Dispose(); + _pendingReplanCts = null; + _pendingReplan = null; + + DiscardLookahead(); + } + + } +} diff --git a/MinecraftClient/Pathing/Execution/PathTransitionHints.cs b/MinecraftClient/Pathing/Execution/PathTransitionHints.cs new file mode 100644 index 0000000000..28a58e6455 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathTransitionHints.cs @@ -0,0 +1,25 @@ +namespace MinecraftClient.Pathing.Execution +{ + public sealed record PathTransitionHints( + int DesiredHeadingX, + int DesiredHeadingZ, + double MinExitSpeed, + double MaxExitSpeed, + bool RequireStableFooting, + bool RequireGrounded, + bool RequireJumpReady, + bool AllowAirBrake, + int HorizonTicks) + { + public static PathTransitionHints Default { get; } = new( + DesiredHeadingX: 0, + DesiredHeadingZ: 0, + MinExitSpeed: 0.0, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: false, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 8); + } +} diff --git a/MinecraftClient/Pathing/Execution/PathTransitionType.cs b/MinecraftClient/Pathing/Execution/PathTransitionType.cs new file mode 100644 index 0000000000..f099c0ac89 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/PathTransitionType.cs @@ -0,0 +1,11 @@ +namespace MinecraftClient.Pathing.Execution +{ + public enum PathTransitionType + { + FinalStop, + ContinueStraight, + Turn, + PrepareJump, + LandingRecovery + } +} diff --git a/MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs b/MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs new file mode 100644 index 0000000000..f65fd25bc9 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using MinecraftClient.Mapping; + +namespace MinecraftClient.Pathing.Execution.Telemetry +{ + public interface IPathExecutionObserver + { + void OnNavigationStarted(IReadOnlyList segments); + void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment); + void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position); + void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position); + void OnNavigationCompleted(int totalTicks); + void OnReplanStarted(int replanCount, Location position); + void OnReplanSucceeded(int replanCount, IReadOnlyList segments); + void OnReplanFailed(int replanCount, Location position); + } +} diff --git a/MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs b/MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs new file mode 100644 index 0000000000..bf083717a1 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using MinecraftClient.Mapping; + +namespace MinecraftClient.Pathing.Execution.Telemetry +{ + public sealed class PathExecutionLogObserver : IPathExecutionObserver + { + private readonly Action? _debug; + + public PathExecutionLogObserver(Action? debug) => _debug = debug; + + public void OnNavigationStarted(IReadOnlyList segments) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_route_start, + segments.Count)); + + public void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_segment_start, + segmentIndex, + totalSegments, + segment.MoveType, + segment.ExitTransition)); + + public void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_segment_complete, + segmentIndex, + totalSegments, + segment.MoveType, + elapsedTicks, + position.X, + position.Y, + position.Z)); + + public void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_segment_failed, + segmentIndex, + totalSegments, + segment.MoveType, + elapsedTicks, + position.X, + position.Y, + position.Z)); + + public void OnNavigationCompleted(int totalTicks) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_route_complete, + totalTicks)); + + public void OnReplanStarted(int replanCount, Location position) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_replan_start, + replanCount, + position.X, + position.Y, + position.Z)); + + public void OnReplanSucceeded(int replanCount, IReadOnlyList segments) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_replan_success, + replanCount, + segments.Count)); + + public void OnReplanFailed(int replanCount, Location position) => + _debug?.Invoke(string.Format( + CultureInfo.InvariantCulture, + Translations.pathing_metric_replan_failed, + replanCount, + position.X, + position.Y, + position.Z)); + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs new file mode 100644 index 0000000000..b4af812ee6 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs @@ -0,0 +1,281 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Jump up 1 block while moving 1 block in a cardinal direction. + /// Faces destination, sprints forward, and jumps when on ground. + /// + /// Follows Baritone's MovementAscend.updateState gating: + /// - jump immediately when headBonkClear (no low-ceiling hazard above source) + /// - otherwise wait until close to the destination edge (flatDistToNext <= 1.2) + /// and laterally lined up (sideDist <= 0.2) before firing the jump + /// This avoids bonking the ceiling on short staircases and avoids jumping while + /// still too far away (which causes the short-hop to stall against the riser). + /// + public sealed class AscendTemplate : IActionTemplate + { + private const double EdgeCloseDistance = 1.2; + private const double LateralAlignmentTolerance = 0.2; + + // Diagonal-ascend velocity alignment constants. Live-server + // regression: when A* routes through an "island" diagonal 1-block + // riser whose preceding segment delivered axis-aligned ground + // momentum (e.g. a cardinal Traverse along +Z landing at the foot of + // a -X+Z+Y riser), the 1-tick sprint-jump boost cannot redirect the + // perpendicular component onto the diagonal and the bot overshoots + // the target along the cardinal axis. Before firing Jump we hold + // Forward/Sprint off for up to a small window so ground friction can + // decay the perpendicular component; if we have not aligned within + // the window we take off anyway so the bot never stalls on the + // source block indefinitely. + private const double DiagonalTakeoffMaxPerpVelocity = 0.08; + private const int DiagonalTakeoffMaxBrakeTicks = 6; + + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private Location _lastPos; + private int _stuckTicks; + private bool _initiatedJump; + private bool _hasBeenAirborne; + private int _diagonalBrakeTicks; + + public AscendTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + _lastPos = segment.Start; + } + + 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; + double horizDistSq = dx * dx + dz * dz; + + bool groundedPrepareJumpHandoff = physics.OnGround + && Math.Abs(dy) < 0.2 + && _segment.ExitTransition == PathTransitionType.PrepareJump + && _segment.ExitHints.RequireJumpReady + && TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End); + + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + if (!groundedPrepareJumpHandoff) + { + // Snap yaw on the first tick so we don't drift sideways while + // rotating from a stale orientation (e.g. after a teleport or a + // sharp turn transition). The Ascend template also already gates + // forward input on headingReady below, but snapping removes one + // source of wasted ticks for narrow 1-block staircases. + physics.Yaw = _tickCount == 1 + ? targetYaw + : TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + } + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); + float headingPenalty = YawDifference(physics.Yaw, targetYaw); + bool headingReady = headingPenalty <= 8.0; + bool turnInPlace = !_initiatedJump && !headingReady; + input.Forward = !turnInPlace; + input.Sprint = !turnInPlace; + + if (physics.OnGround && dy > 0.1) + { + bool diagonalAscend = _segment.HeadingX != 0 && _segment.HeadingZ != 0; + double flatDistToNext = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + double sideDist = TemplateHelper.LateralOffsetFromSegmentLine(pos, _segment); + + bool closeToEdge = flatDistToNext <= EdgeCloseDistance; + bool laterallyAligned = sideDist <= LateralAlignmentTolerance; + + bool jumpReady; + if (diagonalAscend) + { + // Diagonal Ascend only reaches this path when the search + // layer has cleared the move (cardinal split not + // feasible). The source-center to target-center distance + // is ~sqrt(2) blocks, so the cardinal closeToEdge / + // sideDist gates below never fire and would stall the + // jump indefinitely; the bot must leap from the source + // block center as soon as its heading is aligned with + // the diagonal AND the horizontal velocity is close to + // the diagonal direction. If the bot arrives with strong + // cardinal momentum from a preceding Traverse (the + // common case for wall-shoulder islands), fire one or + // more ground ticks with Forward/Sprint released so + // friction can decay the perpendicular component before + // takeoff. Without this the preserved cardinal momentum + // leaks the landing footprint off the target block. + if (!headingReady) + { + jumpReady = false; + } + else + { + double diagLen = Math.Sqrt( + (double)_segment.HeadingX * _segment.HeadingX + + (double)_segment.HeadingZ * _segment.HeadingZ); + double dirX = _segment.HeadingX / diagLen; + double dirZ = _segment.HeadingZ / diagLen; + double vx = physics.DeltaMovement.X; + double vz = physics.DeltaMovement.Z; + double perpMag = Math.Abs(vx * dirZ - vz * dirX); + if (perpMag > DiagonalTakeoffMaxPerpVelocity + && _diagonalBrakeTicks < DiagonalTakeoffMaxBrakeTicks) + { + // Suppress this tick's acceleration so vanilla + // ground friction (~0.546/tick) alone decays the + // perpendicular component, and nudge the back + // input if the velocity is dominantly in the + // perpendicular direction - the back-input vector + // is along -yaw which is the reverse of the + // diagonal, cancelling the perpendicular faster + // than friction alone for high-speed entries. + input.Forward = false; + input.Sprint = false; + double along = vx * dirX + vz * dirZ; + if (perpMag > Math.Abs(along)) + input.Back = true; + _diagonalBrakeTicks++; + jumpReady = false; + } + else + { + jumpReady = true; + } + } + } + else if (HasHeadBonkClear(world)) + { + // Vertical head-room above the source block is clear, so starting the + // jump early is safe and actually makes the short hop more reliable + // (matches Baritone's "headBonkClear" shortcut). + jumpReady = headingReady; + } + else + { + // Mirror Baritone's gate: only jump when close to the riser and + // laterally lined up; otherwise we end up banging the side of the + // block without gaining height. + jumpReady = headingReady && closeToEdge && laterallyAligned; + } + + if (jumpReady) + { + // Snap rotation to the target direction on the takeoff tick so + // the sprint-jump boost goes along the segment line regardless of + // how many ticks the smoothing had to consume. Baritone sets + // rotation directly every tick and the server accepts it. + physics.Yaw = targetYaw; + input.Jump = true; + _initiatedJump = true; + } + } + + if (!physics.OnGround) + _hasBeenAirborne = true; + + if (physics.OnGround && Math.Abs(dy) < 0.2) + { + // Post-landing shortcut for Turn exits only: once the Ascend's + // jump arc has put the bot back on ground at the target's + // elevation with its center inside the target column, hand off + // to the next template (which snaps yaw on its first tick) + // instead of trying to settle to stable footing. + // + // Holding onto the segment here re-runs both the AscendTemplate + // top-level yaw smoothing toward targetYaw (a moving bearing as + // the bot drifts past End) AND GroundedSegmentController's + // exit-heading rotation each tick. With a Turn transition the + // two yaw targets disagree (segment heading vs perpendicular + // exit heading) and the bot oscillates ~80 ticks until it + // walks off the 1-block landing's edge and the segment fails. + // + // We deliberately do NOT shortcut PrepareJump exits: the next + // segment is another jump that needs the bot settled near the + // target column center for a clean takeoff. Completing too + // early leaves the bot's start position offset along the + // previous heading, which compounds with the next segment's + // sprint-jump boost and overshoots short (2-block) parkour + // landings. ContinueStraight/LandingRecovery share the same + // segment heading as the next segment, so the GSC handoff + // does not produce a conflicting yaw target. + if (_hasBeenAirborne + && _segment.ExitTransition == PathTransitionType.Turn + && TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End)) + { + return TemplateState.Complete; + } + + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + } + + double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos); + double movedY = Math.Abs(pos.Y - _lastPos.Y); + _stuckTicks = (movedSq < 0.0005 && movedY < 0.001) ? _stuckTicks + 1 : 0; + _lastPos = pos; + + // Baritone tolerates up to 200 ticks (MAX_TICKS_AWAY) before abandoning a + // movement. We mirror that budget so the template does not fail spuriously + // during normal run-up / jump / landing settle flows. + if (_stuckTicks > 120 || _tickCount > 200) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + /// + /// True when no solid block sits two cells above the source ascent position + /// in any cardinal direction the player might nick while rising. Mirrors + /// Baritone's MovementAscend.headBonkClear. + /// + private bool HasHeadBonkClear(World world) + { + int sx = (int)Math.Floor(ExpectedStart.X); + int sy = (int)Math.Floor(ExpectedStart.Y); + int sz = (int)Math.Floor(ExpectedStart.Z); + + // Directly above the source block and each cardinal neighbour at head + // height must be walkable-through so the player never catches a corner. + if (!IsWalkThroughAt(world, sx, sy + 2, sz)) + return false; + + int[] dx = { 1, -1, 0, 0 }; + int[] dz = { 0, 0, 1, -1 }; + for (int i = 0; i < 4; i++) + { + if (!IsWalkThroughAt(world, sx + dx[i], sy + 2, sz + dz[i])) + return false; + } + + return true; + } + + private static bool IsWalkThroughAt(World world, int x, int y, int z) + { + Block block = world.GetBlock(new Location(x, y, z)); + return !block.Type.IsSolid(); + } + + private static float YawDifference(float current, float target) + { + float delta = target - current; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs new file mode 100644 index 0000000000..4e6c39912f --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs @@ -0,0 +1,84 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Climb up or down a ladder/vine by 1 block. + /// Up: pushes against the wall (Forward + face center) and jumps. + /// Down: releases all input to let gravity + climbable friction handle descent. + /// + public sealed class ClimbTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly bool _goingUp; + private int _tickCount; + + public ClimbTemplate(PathSegment segment, PathSegment? nextSegment) + { + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + _goingUp = segment.End.Y > segment.Start.Y; + } + + public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + _tickCount++; + + double dy = ExpectedEnd.Y - pos.Y; + double dx = ExpectedEnd.X - pos.X; + double dz = ExpectedEnd.Z - pos.Z; + double horizDistSq = dx * dx + dz * dz; + + if (Math.Abs(dy) < 0.4 && horizDistSq < 0.5) + return TemplateState.Complete; + + if (_tickCount > 120) + return TemplateState.Failed; + + float targetPitch = _goingUp ? -70f : 70f; + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); + + if (physics.OnClimbable) + { + if (_goingUp) + { + input.Jump = true; + input.Forward = true; + if (horizDistSq > 0.01) + { + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + } + } + else + { + // Descending: release all input, gravity pulls down at clamped speed. + // Do NOT press Sneak (that would freeze position on ladders). + // Do NOT press Jump (that would push upward). + // Keep centered horizontally by gently steering if drifting. + if (horizDistSq > 0.15) + { + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + input.Forward = true; + } + } + } + else + { + if (horizDistSq > 0.01) + { + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + input.Forward = true; + } + } + + return TemplateState.InProgress; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs new file mode 100644 index 0000000000..bf44286c3b --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs @@ -0,0 +1,286 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Walk off a ledge and drop 1-N blocks to a landing spot. + /// Walks toward the destination; gravity handles the fall. + /// Sprints when the horizontal distance is large (> 1.5 blocks). + /// Supports solid landings, water landings, and mid-fall vine/ladder grabs. + /// + public sealed class DescendTemplate : IActionTemplate + { + private const float PreDropYawToleranceDeg = 12f; + + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private bool _hasFallen; + private readonly bool _needsSprint; + + public DescendTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + double hdx = segment.End.X - segment.Start.X; + double hdz = segment.End.Z - segment.Start.Z; + _needsSprint = (hdx * hdx + hdz * hdz) > 2.25; + } + + 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; + double horizDistSq = dx * dx + dz * dz; + + if (!physics.OnGround) + _hasFallen = true; + + // Completion: landed in water near destination + if (_hasFallen && physics.InWater && horizDistSq < 0.5 && Math.Abs(dy) < 2.0) + return TemplateState.Complete; + + // Fail if climbing up instead of descending + if (pos.Y > ExpectedStart.Y + 2.0) + return TemplateState.Failed; + + if (_tickCount > 200) + return TemplateState.Failed; + + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); + + // Snap yaw on the first tick to avoid a few ticks of sideways drift + // when the bot enters this segment with a stale orientation (e.g. + // just after a teleport or after a turn). Ledge-adjacent descends + // cannot tolerate drift without falling off the wrong side. + if (_tickCount == 1) + physics.Yaw = targetYaw; + + if (physics.OnGround && Math.Abs(dy) < (_hasFallen ? 1.0 : 0.6)) + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + bool onOrPastTarget = TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + || TemplateHelper.HasReachedSegmentEndPlane(pos, _segment); + + // Fallback: after a diagonal descend landing the bot can end + // up on a support block that is not yet the target block + // (footprint still off the landing column). The braking + // planner reads "remaining <= coastStop + lead" and returns + // Coast, which zeroes every input - if the bot has already + // come to rest this means the segment hangs forever and the + // pathing manager replans. When we are stopped, not inside + // the target block, and not being asked to brake, walk + // toward the target instead of coasting so the landing + // resolves in one tick-window. + if (!decision.HoldBack + && !TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + && horizDistSq > 0.01 + && TemplateHelper.GetHorizontalSpeed(physics) < 0.03) + { + float walkYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, walkYaw); + input.Forward = true; + input.Sprint = _needsSprint; + + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + return TemplateState.InProgress; + } + + if (horizDistSq > 0.01 && !decision.HoldBack) + { + float groundedYaw = onOrPastTarget + ? TemplateHelper.GetExitHeadingYaw(_segment) + : TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment) + ? TemplateHelper.GetExitHeadingYaw(_segment) + : targetYaw; + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, groundedYaw); + } + + TemplateHelper.ApplyDecision(input, decision); + if (decision.HoldBack) + TemplateHelper.FaceSegmentHeading(physics, _segment); + + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + } + else if (physics.OnClimbable) + { + if (horizDistSq > 0.25) + { + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + input.Forward = true; + } + } + else if (horizDistSq > 0.01) + { + bool onOrPastTarget = TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + || TemplateHelper.HasReachedSegmentEndPlane(pos, _segment); + // Airborne bias toward the exit heading is only safe when + // the bot has effectively finished the current segment's + // horizontal travel: either the footprint is inside the + // landing block, or the vertical drop is small enough that + // lateral drift cannot miss the 1x1 landing column. For + // multi-block drops the bot is in the air for 8+ ticks; + // rotating yaw mid-fall (e.g. after crossing the end plane + // but still 1-2 blocks above landing) pushes sprint/walk + // momentum perpendicular to the segment and drifts the bot + // off the landing column into the void. Keep yaw pointed + // at the landing center through the whole fall on multi-Y + // descends; GroundedSegmentController rotates yaw once the + // bot is actually standing on the landing column. + double segmentYDrop = _segment.Start.Y - _segment.End.Y; + bool isSingleStepDescend = segmentYDrop <= 1.0; + bool footInsideTarget = TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd); + + // Long-descend lateral drift guard. When the bot's footprint + // enters the landing block at the very start of a multi-block + // fall (e.g. a 22-block water drop where target X/Z column + // matches the launch column), `biasTowardExitInAir` would + // immediately rotate yaw to the next segment's heading. With + // Forward held during the entire fall, the perpendicular air + // drift accumulates ~0.05 m/tick and over 20+ airborne ticks + // walks the bot a full block out of the landing column, so it + // misses the water/landing target and dies on the rim. Only + // permit exit-heading bias for non-single-step descends once + // the bot is within ~1.5 m of the landing Y (~3 ticks of + // free-fall), so any exit-heading drift cannot displace the + // landing footprint by more than a fraction of a block. + double remainingFallY = pos.Y - _segment.End.Y; + bool nearLanding = remainingFallY <= 1.5; + + bool biasTowardExitInAir = (footInsideTarget && (isSingleStepDescend || nearLanding)) + || (isSingleStepDescend + && (onOrPastTarget + || (_hasFallen + && TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment, distanceThreshold: 1.5)))); + float airborneYaw; + if (biasTowardExitInAir) + { + airborneYaw = TemplateHelper.GetExitHeadingYaw(_segment); + } + else if (!isSingleStepDescend) + { + // Multi-block descend: target-tracking yaw rotates as + // the bot drifts past the landing column mid-fall (e.g. + // a diagonal 3 c2c drop with dx=-1, dz=-1 starts at + // yaw=135, the relative bearing to End flips through + // 90 -> 0 -> 315 in 6 air ticks). With Forward input + // held, the rotating yaw pushes air-control momentum + // perpendicular to the planned trajectory, drifting + // the bot ~0.5 m past the landing column and onto an + // adjacent block one tier below. Lock airborne yaw to + // the segment's start->end heading so air drift stays + // aligned with the planned diagonal; the GroundedSegment + // controller takes over once the bot is on the landing. + airborneYaw = TemplateHelper.CalculateYaw(_segment.HeadingX, _segment.HeadingZ); + } + else + { + airborneYaw = targetYaw; + } + + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, airborneYaw); + if (_hasFallen || YawDifference(physics.Yaw, airborneYaw) <= PreDropYawToleranceDeg) + { + if (!_hasFallen && !_needsSprint && ShouldCoastOffLedge(pos)) + { + // For short descends into a stop or turn, release forward near the lip + // so the landing stays on the intended support instead of overshooting it. + } + else if (!_hasFallen && !_needsSprint) + { + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + } + else + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + // Multi-block descend overshoot guard: when the + // fall spans 2+ Y blocks, sprint momentum will + // carry the bot roughly one extra horizontal + // block past the planned landing. If the next + // segment prepares a jump (PrepareJump exit) the + // bot MUST land inside the planned 1x1 landing + // column so the jump takeoff has a valid footing; + // overshooting drops into the void or onto a + // block 1-2 tiers below, breaking the jump. + // Once airborne and past the landing end-plane, + // release forward input so sprint momentum decays + // via air drag over the final 1-2 ticks of fall, + // pulling the bot back into the landing column. + // + // The same guard applies to long water/landing + // drops with any non-PrepareJump exit. A 22-block + // fall lasts 20+ airborne ticks; at ~0.2 m/tick + // peak air-control velocity, holding Forward for + // the entire fall accumulates 4+ m of horizontal + // drift past the start ledge and the bot lands + // outside the 1x1 water column. Once the + // footprint is inside the target column, brake + // horizontal velocity so the bot falls straight + // down into the water/landing block. + bool riskyOvershoot = _hasFallen + && segmentYDrop >= 2.0 + && onOrPastTarget + && _segment.ExitTransition == PathTransitionType.PrepareJump; + bool longFallFootprintLanding = _hasFallen + && segmentYDrop >= 2.0 + && footInsideTarget; + if (riskyOvershoot || longFallFootprintLanding) + { + input.Forward = false; + input.Sprint = false; + input.Back = true; + } + else if (_segment.ExitHints.AllowAirBrake) + { + TemplateHelper.ApplyDecision(input, decision); + if (decision.HoldForward && _needsSprint) + input.Sprint = true; + } + else + { + input.Forward = true; + if (_needsSprint) + input.Sprint = true; + } + } + } + } + + return TemplateState.InProgress; + } + + private bool ShouldCoastOffLedge(Location pos) + { + if (_segment.ExitTransition == PathTransitionType.ContinueStraight) + return false; + + double remaining = (_segment.End.X - pos.X) * _segment.HeadingX + + (_segment.End.Z - pos.Z) * _segment.HeadingZ; + return remaining <= 0.55 + && TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, _segment.End); + } + + private static float YawDifference(float current, float target) + { + float delta = target - current; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs new file mode 100644 index 0000000000..0380ea06ae --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs @@ -0,0 +1,51 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Vertical free fall at the same X,Z. Waits for the player to land at the target Y. + /// Supports both solid ground landings and water landings. + /// + public sealed class FallTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private int _tickCount; + private bool _hasFallen; + + public FallTemplate(PathSegment segment, PathSegment? nextSegment) + { + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + } + + 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 = pos.Y - ExpectedEnd.Y; + double horizDistSq = dx * dx + dz * dz; + + if (!physics.OnGround) + _hasFallen = true; + + // Solid ground landing near the target XZ + if (_hasFallen && physics.OnGround && Math.Abs(dy) < 1.0 && horizDistSq < 1.0) + return TemplateState.Complete; + + // Water landing near the target XZ + if (_hasFallen && physics.InWater && Math.Abs(dy) < 2.0 && horizDistSq < 1.5) + return TemplateState.Complete; + + if (_tickCount > 200) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs b/MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs new file mode 100644 index 0000000000..87c0340330 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs @@ -0,0 +1,187 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + internal static class GroundedSegmentController + { + private const double FinalStopFastCompleteSpeed = 0.08; + private const double PrepareJumpHandoffDistance = 0.40; + + internal static void Apply(PathSegment segment, PathSegment? nextSegment, Location pos, PlayerPhysics physics, MovementInput input, World world) + { + 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); + return; + } + + // Compute the braking decision first so rotation and input stay + // consistent. Applying the exit-heading bias while we are still + // braking causes the Back input (which acts opposite to yaw) to + // push the bot perpendicular to the segment line, which on narrow + // 1-block walkways turns into a side-off-the-edge step. Stay on + // the segment heading for as long as we are braking and only let + // the bias rotate us once the brake has released. + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(segment, nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + if (decision.HoldBack) + { + TemplateHelper.FaceSegmentHeading(physics, segment); + return; + } + + // On stable-footing Turn exits (the next segment is a walk-like + // move, not a jump) the next template snaps yaw instantly on + // its first tick, so pre-rotating here is unnecessary. On + // narrow 1-block walkways the bias combined with along-segment + // momentum pushes the bot perpendicular to the walkway and + // walks it off the edge (the bot sprint-drifts diagonally + // while yaw rotates ~45 deg mid-stride). For Turn exits into + // a jump (RequireJumpReady) we still need to align yaw before + // takeoff, so keep the bias there. + bool suppressBiasForSafeTurn = segment.ExitTransition == PathTransitionType.Turn + && segment.ExitHints.RequireStableFooting + && !segment.ExitHints.RequireJumpReady; + if (!suppressBiasForSafeTurn + && TemplateHelper.ShouldBiasTowardExitHeading(pos, segment)) + TemplateHelper.FaceExitHeading(physics, segment); + } + + internal static bool ShouldComplete(PathSegment segment, Location pos, PlayerPhysics physics) + { + if (segment.ExitHints.RequireGrounded && !physics.OnGround) + return false; + + if (segment.ExitTransition == PathTransitionType.ContinueStraight + && physics.OnGround) + { + if (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, segment.End) + && !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, segment.End)) + { + return true; + } + + if (TemplateHelper.IsSettledAtEnd(pos, segment.End, physics)) + return true; + } + + if (segment.ExitTransition == PathTransitionType.FinalStop + && physics.OnGround + && TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, segment.End) + && !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, segment.End)) + { + return TemplateHelper.GetHorizontalSpeed(physics) <= FinalStopFastCompleteSpeed; + } + + double exitSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHint(physics, segment); + // On Turn exits we deliberately do NOT pre-rotate yaw toward the + // next segment's heading (see Apply() above). The next segment's + // template snaps yaw on its first tick, so measuring heading + // readiness against the exit heading here would deadlock the + // handoff (bot is still facing segment heading, would never pass + // the 15 deg gate). Measure against segment heading for Turn + // exits with stable footing where yaw will be snapped anyway. + bool headingReady; + if (segment.ExitTransition == PathTransitionType.Turn + && segment.ExitHints.RequireStableFooting + && !segment.ExitHints.RequireJumpReady) + { + headingReady = TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment.HeadingX, segment.HeadingZ) <= 25.0 + || TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment) <= 15.0; + } + else + { + headingReady = TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment) + <= (segment.ExitHints.RequireJumpReady ? 8.0 : 15.0); + } + + if (!headingReady) + return false; + + if (segment.ExitTransition == PathTransitionType.PrepareJump + && physics.OnGround + && segment.ExitHints.RequireJumpReady + && TemplateFootingHelper.IsCenterInsideTargetBlock(pos, segment.End)) + { + if (segment.MoveType == MoveType.Ascend) + return true; + + double handoffDistance = segment.MoveType == MoveType.Parkour + ? 0.55 + : PrepareJumpHandoffDistance; + + return TemplateHelper.RemainingDistanceAlongSegment(pos, segment) <= handoffDistance; + } + + // LandingRecovery accepts a fully-decelerated handoff: once the bot + // has reached the target block on the ground, the segment has done + // its job. Apply this before the MinExitSpeed gate so a Descend + // that lands and naturally settles to zero speed (e.g. when the + // following segment is a fresh Traverse rather than a chained + // Parkour) can hand off cleanly. Without this early-out the bot + // would idle inside the destination block until the segment timed + // out, triggering an unnecessary replan. + if (segment.ExitTransition == PathTransitionType.LandingRecovery + && physics.OnGround + && !segment.ExitHints.RequireStableFooting + && TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, segment.End)) + { + return true; + } + + if (exitSpeed < segment.ExitHints.MinExitSpeed) + return false; + + if (exitSpeed > segment.ExitHints.MaxExitSpeed) + return false; + + if (segment.ExitHints.RequireStableFooting) + { + return physics.OnGround + && (segment.ExitTransition == PathTransitionType.FinalStop + ? TemplateHelper.IsSettledAtEnd(pos, segment.End, physics) + : TemplateHelper.IsSettledOnTargetBlock(pos, segment.End, physics)); + } + + if (segment.ExitHints.RequireJumpReady) + { + return physics.OnGround + && TemplateHelper.HasReachedSegmentEndPlane(pos, segment) + && exitSpeed >= segment.ExitHints.MinExitSpeed; + } + + return segment.ExitTransition switch + { + PathTransitionType.ContinueStraight => TemplateHelper.IsNear(pos, segment.End, horizThresholdSq: 0.09), + PathTransitionType.PrepareJump => TemplateHelper.HasReachedSegmentEndPlane(pos, segment) + && exitSpeed > 0.02, + PathTransitionType.FinalStop => physics.OnGround && TemplateHelper.IsSettledAtEnd(pos, segment.End, physics), + _ => physics.OnGround && TemplateHelper.IsSettledOnTargetBlock(pos, segment.End, physics) + }; + } + + private static bool IsReadyToFreezeForTurn(PathSegment segment, Location pos) + { + if (segment.MoveType == MoveType.Ascend) + return true; + + if (segment.MoveType == MoveType.Parkour + || (segment.HeadingX != 0 && segment.HeadingZ != 0)) + { + return TemplateHelper.RemainingDistanceAlongSegment(pos, segment) <= PrepareJumpHandoffDistance; + } + + return true; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/SidewallParkourController.cs b/MinecraftClient/Pathing/Execution/Templates/SidewallParkourController.cs new file mode 100644 index 0000000000..0c42671551 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/SidewallParkourController.cs @@ -0,0 +1,684 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + internal sealed class SidewallParkourController + { + private enum PrepareJumpAirProfile + { + Hold, + Coast, + Release + } + + private enum Phase + { + Approach, + Airborne, + Landing + } + + internal Location ExpectedStart { get; } + internal Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private readonly double _horizDist; + private readonly double _dominantDist; + private readonly float _takeoffYaw; + private readonly float _nominalLandingYaw; + + private int _tickCount; + private Phase _phase = Phase.Approach; + private bool _leftGround; + private bool _carriedGroundEntry; + private bool _releaseForwardLatched; + + private const float YawToleranceDeg = 5f; + private const float MaxYawStepPerTick = 20f; + private const double CarryRunwayThreshold = 0.10; + + internal SidewallParkourController(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + + double dx = segment.End.X - segment.Start.X; + double dz = segment.End.Z - segment.Start.Z; + _horizDist = Math.Sqrt(dx * dx + dz * dz); + _dominantDist = Math.Max(Math.Abs(dx), Math.Abs(dz)); + double dropHeight = segment.Start.Y - segment.End.Y; + _nominalLandingYaw = TemplateHelper.CalculateYaw(dx, dz); + _takeoffYaw = nextSegment is not null + && _dominantDist >= 5.0 + && dropHeight > 0.0 + && dropHeight < 1.5 + ? TemplateHelper.GetApproachYaw(segment) + : TemplateHelper.GetSidewallTakeoffYaw(segment); + } + + internal TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + _tickCount++; + + if (_tickCount == 1 && TemplateHelper.GetHorizontalSpeed(physics) > 0.02) + _carriedGroundEntry = true; + + double dx = ExpectedEnd.X - pos.X; + double dz = ExpectedEnd.Z - pos.Z; + double dy = ExpectedEnd.Y - pos.Y; + double carryApproachProgress = _tickCount == 1 + ? TemplateHelper.ProgressAlongApproach(pos, _segment) + : 0.0; + + if (_phase != Phase.Landing) + { + float activeYaw; + if (_phase == Phase.Approach + && _carriedGroundEntry + && _tickCount == 1 + && carryApproachProgress < CarryRunwayThreshold) + { + activeYaw = TemplateHelper.GetApproachYaw(_segment); + } + else if (_phase == Phase.Airborne && ShouldUseLandingYaw(pos)) + { + activeYaw = GetLandingYaw(pos); + } + else + { + activeYaw = _takeoffYaw; + } + + float yawStep = _phase == Phase.Airborne ? 20f : MaxYawStepPerTick; + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, activeYaw, maxStep: yawStep); + physics.Pitch = TemplateHelper.SmoothPitch( + physics.Pitch, + TemplateHelper.CalculatePitch(dx, dy, dz)); + } + + switch (_phase) + { + case Phase.Approach: + return TickApproach(pos, physics, input, world); + + case Phase.Airborne: + return TickAirborne(pos, physics, input, world); + + case Phase.Landing: + return TickLanding(pos, physics, input, world); + + default: + return TemplateState.Failed; + } + } + + private TemplateState TickApproach(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (!physics.OnGround && ShouldRecoverGroundApproach(pos, physics)) + { + physics.OnGround = true; + if (physics.DeltaMovement.Y < 0.0) + physics.DeltaMovement = new Vec3d(physics.DeltaMovement.X, 0.0, physics.DeltaMovement.Z); + } + + if (!physics.OnGround) + { + _leftGround = true; + _phase = Phase.Airborne; + return TickAirborne(pos, physics, input, world); + } + + float yawDelta = YawDifference(physics.Yaw, _takeoffYaw); + bool turnInPlace = yawDelta > 35f; + input.Forward = !turnInPlace; + input.Sprint = !turnInPlace; + + double minApproachDistance = GetMinApproachDistance(); + double approachProgress = TemplateHelper.ProgressAlongApproach(pos, _segment); + double approachSpeed = GetApproachSpeed(physics); + if (_carriedGroundEntry && _tickCount == 1 && approachProgress < CarryRunwayThreshold) + { + input.Sprint = false; + return TemplateState.InProgress; + } + + if (yawDelta < YawToleranceDeg + && approachProgress >= minApproachDistance + && approachSpeed >= GetMinTakeoffApproachSpeed()) + { + if (ShouldApplyLaunchStrafe()) + { + physics.Yaw = TemplateHelper.GetApproachYaw(_segment); + ApplyAirStrafe(physics, input); + } + + if (ShouldSuppressSprintJumpTakeoff()) + input.Sprint = false; + input.Jump = true; + _phase = Phase.Airborne; + } + + if (_tickCount > 40) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + private TemplateState TickAirborne(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (!physics.OnGround) + _leftGround = true; + + bool pastTarget = IsPastTarget(pos); + if (ShouldApplyAirStrafe(pos)) + ApplyAirStrafe(physics, input); + + if (_nextSegment is null) + { + bool shouldRelease = ShouldReleaseInAir(pos, physics, world); + _releaseForwardLatched |= shouldRelease; + + if (_releaseForwardLatched || pastTarget) + { + input.Forward = false; + input.Sprint = false; + } + else + { + input.Forward = true; + input.Sprint = true; + } + } + else + { + switch (ChoosePrepareJumpAirProfile(pos, physics, world)) + { + case PrepareJumpAirProfile.Release: + input.Forward = false; + input.Sprint = false; + break; + + case PrepareJumpAirProfile.Coast: + input.Forward = true; + input.Sprint = false; + break; + + default: + input.Forward = true; + input.Sprint = true; + break; + } + } + + if (_leftGround && physics.OnGround) + { + _phase = Phase.Landing; + return TickLanding(pos, physics, input, world); + } + + if (pos.Y < ExpectedEnd.Y - 4.0 || _tickCount > 60) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + private bool ShouldApplyAirStrafe(Location pos) + { + if (ExpectedEnd.Y >= ExpectedStart.Y) + return false; + + if (!NeedsAdditionalLateralBias(pos)) + return false; + + if (_dominantDist >= 5.0) + { + double dropHeight = ExpectedStart.Y - ExpectedEnd.Y; + double lateStrafeThreshold = _nextSegment is not null + ? (dropHeight < 1.5 ? 2.30 : 1.55) + : 1.10; + return TemplateHelper.RemainingDistanceAlongSegment(pos, _segment) <= lateStrafeThreshold; + } + + if (_dominantDist < 3.0) + { + double shortDescendThreshold = _nextSegment is not null ? 1.15 : 0.95; + return TemplateHelper.RemainingDistanceAlongSegment(pos, _segment) <= shortDescendThreshold; + } + + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + double threshold = _dominantDist >= 4.0 + ? Math.Max(2.4, _dominantDist * 0.65) + : Math.Max(1.4, _dominantDist * 0.60); + return remaining <= threshold; + } + + private void ApplyAirStrafe(PlayerPhysics physics, MovementInput input) + { + GetAirLateralDirection(out int desiredX, out int desiredZ); + if (desiredX == 0 && desiredZ == 0) + return; + + double yawRad = physics.Yaw * (Math.PI / 180.0); + double rightX = -Math.Cos(yawRad); + double rightZ = -Math.Sin(yawRad); + double projection = (desiredX * rightX) + (desiredZ * rightZ); + if (projection >= 0.0) + input.Right = true; + else + input.Left = true; + } + + private void GetAirLateralDirection(out int desiredX, out int desiredZ) + { + TemplateHelper.GetApproachHeading(_segment, out int headingX, out int headingZ); + if (headingX != 0) + { + desiredX = 0; + desiredZ = Math.Sign(ExpectedEnd.Z - ExpectedStart.Z); + return; + } + + desiredX = Math.Sign(ExpectedEnd.X - ExpectedStart.X); + desiredZ = 0; + } + + private TemplateState TickLanding(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (!physics.OnGround) + { + _phase = Phase.Airborne; + return TickAirborne(pos, physics, input, world); + } + + if (_nextSegment is not null) + return TickPrepareJumpLanding(pos, physics, input, world); + + return TickFinalStopLanding(pos, physics, input, world); + } + + private TemplateState TickPrepareJumpLanding(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (!physics.OnGround) + return TemplateState.InProgress; + + if (NeedsFootingRecovery(pos, physics)) + { + ApplyFootingRecovery(pos, physics, input); + if (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + && !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, ExpectedEnd)) + { + return TemplateState.Complete; + } + + return ContinueOrFail(pos); + } + + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + && GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + + return ContinueOrFail(pos); + } + + private TemplateState TickFinalStopLanding(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + if (!physics.OnGround) + { + return ContinueOrFail(pos); + } + + if (NeedsFootingRecovery(pos, physics)) + { + ApplyFootingRecovery(pos, physics, input); + if (TemplateHelper.IsSettledOnTargetBlock(pos, ExpectedEnd, physics)) + return TemplateState.Complete; + return ContinueOrFail(pos); + } + + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + + return ContinueOrFail(pos); + } + + private double GetMinApproachDistance() + { + if (_carriedGroundEntry) + return _dominantDist >= 4.0 ? 0.18 : 0.08; + + if (_dominantDist >= 4.0) + return 0.10; + + return 0.0; + } + + private double GetMinTakeoffApproachSpeed() + { + if (_dominantDist >= 4.0) + return _carriedGroundEntry ? 0.09 : 0.10; + + if (ExpectedEnd.Y != ExpectedStart.Y) + return _carriedGroundEntry ? 0.08 : 0.09; + + if (_dominantDist >= 3.0) + return _carriedGroundEntry ? 0.09 : 0.08; + + return _carriedGroundEntry ? 0.06 : 0.05; + } + + private double GetApproachSpeed(PlayerPhysics physics) + { + TemplateHelper.GetApproachHeading(_segment, out int headingX, out int headingZ); + return Math.Max(0.0, TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, headingX, headingZ)); + } + + private bool ShouldSuppressSprintJumpTakeoff() + { + if (_dominantDist >= 3.0) + return false; + + if (ExpectedEnd.Y < ExpectedStart.Y) + return true; + + return _nextSegment is null + && ExpectedEnd.Y == ExpectedStart.Y; + } + + private bool ShouldApplyLaunchStrafe() + { + return IsShallowLongDescendingPrepareJump(); + } + + private bool IsShallowLongDescendingPrepareJump() + { + double dropHeight = ExpectedStart.Y - ExpectedEnd.Y; + return _nextSegment is not null + && _dominantDist >= 5.0 + && dropHeight > 0.0 + && dropHeight < 1.5; + } + + private bool NeedsAdditionalLateralBias(Location pos) + { + double halfWidth = PhysicsConsts.PlayerWidth / 2.0; + double blockMinX = Math.Floor(ExpectedEnd.X); + double blockMaxX = blockMinX + 1.0; + double blockMinZ = Math.Floor(ExpectedEnd.Z); + double blockMaxZ = blockMinZ + 1.0; + + TemplateHelper.GetApproachHeading(_segment, out int headingX, out int headingZ); + if (headingZ != 0) + return pos.X - halfWidth < blockMinX || pos.X + halfWidth > blockMaxX; + + return pos.Z - halfWidth < blockMinZ || pos.Z + halfWidth > blockMaxZ; + } + + private bool ShouldRecoverGroundApproach(Location pos, PlayerPhysics physics) + { + if (pos.Y < ExpectedStart.Y - 0.05 || pos.Y > ExpectedStart.Y + 0.05) + return false; + + if (Math.Abs(physics.DeltaMovement.Y) > 0.12) + return false; + + return TemplateFootingHelper.IsCenterInsideTargetBlock(pos, ExpectedStart); + } + + private bool ShouldUseLandingYaw(Location pos) + { + double approachProgress = TemplateHelper.ProgressAlongApproach(pos, _segment); + double activationProgress; + if (_carriedGroundEntry) + { + activationProgress = 0.15; + } + else if (_horizDist <= 2.5) + { + activationProgress = 0.30; + } + else if (_horizDist <= 3.5) + { + activationProgress = 0.45; + } + else + { + activationProgress = 0.60; + } + + if (ExpectedEnd.Y > ExpectedStart.Y) + activationProgress += 0.10; + else if (ExpectedEnd.Y < ExpectedStart.Y) + { + activationProgress = Math.Max(0.20, activationProgress - 0.10); + if (_nextSegment is not null && _dominantDist < 3.0) + { + // Keep the short descending prepare-jump takeoff yaw longer so the late air + // strafe can shave south carry instead of rotating into a south-biased drift. + activationProgress = Math.Max(activationProgress, _dominantDist - 0.55); + } + + if (_nextSegment is not null && _dominantDist >= 5.0) + { + double dropHeight = ExpectedStart.Y - ExpectedEnd.Y; + + // Long descending sidewall jumps only need a small west bias at entry. If we + // rotate into landing yaw too early, we hit the landing block's north face + // before we have enough south depth to climb onto the top. + activationProgress = Math.Max( + activationProgress, + dropHeight < 1.5 ? _dominantDist - 0.35 : _dominantDist - 0.35); + } + } + else if (_dominantDist >= 4.0) + activationProgress = Math.Max(0.45, activationProgress - 0.10); + + return approachProgress >= activationProgress; + } + + private float GetLandingYaw(Location pos) + { + double dx = ExpectedEnd.X - pos.X; + double dz = ExpectedEnd.Z - pos.Z; + if ((dx * dx) + (dz * dz) < 1.0E-6) + return _nominalLandingYaw; + + return TemplateHelper.CalculateYaw(dx, dz); + } + + private bool NeedsFootingRecovery(Location pos, PlayerPhysics physics) + { + if (!physics.OnGround) + return false; + + if (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd)) + return TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, ExpectedEnd); + + if (TemplateFootingHelper.IsCenterInsideTargetBlock(pos, ExpectedEnd)) + return true; + + if (_nextSegment is null + && ExpectedEnd.Y < ExpectedStart.Y + && _segment.ParkourProfile == ParkourProfile.Sidewall + && TemplateHelper.HorizontalDistanceSq(pos, ExpectedEnd) <= 0.81) + { + return true; + } + + return TemplateHelper.HorizontalDistanceSq(pos, ExpectedEnd) <= 0.49; + } + + private void ApplyFootingRecovery(Location pos, PlayerPhysics physics, MovementInput input) + { + float recoveryYaw = GetLandingYaw(pos); + float yawDelta = YawDifference(physics.Yaw, recoveryYaw); + physics.DeltaMovement = new Vec3d(0.0, physics.DeltaMovement.Y, 0.0); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, recoveryYaw, maxStep: MaxYawStepPerTick); + + input.Forward = yawDelta <= 12f; + input.Sprint = false; + input.Back = false; + input.Left = false; + input.Right = false; + } + + private bool IsPastTarget(Location pos) + { + double dirX = ExpectedEnd.X - ExpectedStart.X; + double dirZ = ExpectedEnd.Z - ExpectedStart.Z; + double len = Math.Sqrt(dirX * dirX + dirZ * dirZ); + if (len < 0.001) + return false; + + dirX /= len; + dirZ /= len; + + double relX = pos.X - ExpectedEnd.X; + double relZ = pos.Z - ExpectedEnd.Z; + return relX * dirX + relZ * dirZ > 0.0; + } + + private TemplateState ContinueOrFail(Location pos) + { + if (pos.Y < ExpectedEnd.Y - 4.0 || _tickCount > 60) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + private bool ShouldReleaseInAir(Location pos, PlayerPhysics physics, World world) + { + bool heuristicRelease = _segment.ExitTransition == PathTransitionType.FinalStop + && ShouldReleaseByRemainingLead(pos, physics); + if (heuristicRelease) + return true; + + Location? landingIfHolding = PredictLandingPosition(physics, world, holdForward: true, holdSprint: true); + Location? landingIfReleased = PredictLandingPosition(physics, world, holdForward: false, holdSprint: false); + if (landingIfHolding is null || landingIfReleased is null) + return false; + + bool holdingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfHolding.Value, ExpectedEnd); + bool releasingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfReleased.Value, ExpectedEnd); + return !holdingStaysInside && releasingStaysInside; + } + + private PrepareJumpAirProfile ChoosePrepareJumpAirProfile(Location pos, PlayerPhysics physics, World world) + { + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + double releaseThreshold = _dominantDist >= 4.0 ? 1.05 : 0.55; + double coastThreshold = _dominantDist >= 4.0 ? 1.50 : 0.90; + + if (ExpectedEnd.Y < ExpectedStart.Y) + { + if (_dominantDist < 3.0) + { + releaseThreshold += 0.75; + coastThreshold += 0.75; + } + else if (_dominantDist >= 5.0) + { + double dropHeight = ExpectedStart.Y - ExpectedEnd.Y; + if (dropHeight < 1.5) + { + releaseThreshold = Math.Max(0.50, releaseThreshold - 0.55); + coastThreshold = Math.Max(0.85, coastThreshold - 0.65); + } + else + { + releaseThreshold = Math.Max(0.60, releaseThreshold - 0.45); + coastThreshold = Math.Max(0.95, coastThreshold - 0.45); + } + } + else + { + releaseThreshold += 0.20; + coastThreshold += 0.25; + } + } + + if (remaining <= releaseThreshold) + return PrepareJumpAirProfile.Release; + + if (remaining <= coastThreshold) + return PrepareJumpAirProfile.Coast; + + Location? landingIfHolding = PredictLandingPosition(physics, world, holdForward: true, holdSprint: true); + Location? landingIfCoasting = PredictLandingPosition(physics, world, holdForward: true, holdSprint: false); + Location? landingIfReleased = PredictLandingPosition(physics, world, holdForward: false, holdSprint: false); + + if (landingIfHolding is null || landingIfCoasting is null || landingIfReleased is null) + return PrepareJumpAirProfile.Hold; + + bool holdingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfHolding.Value, ExpectedEnd); + bool coastingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfCoasting.Value, ExpectedEnd); + bool releasingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfReleased.Value, ExpectedEnd); + + if (holdingStaysInside) + return PrepareJumpAirProfile.Hold; + + if (coastingStaysInside) + return PrepareJumpAirProfile.Coast; + + if (releasingStaysInside) + return PrepareJumpAirProfile.Release; + + double holdDistance = TemplateHelper.HorizontalDistanceSq(landingIfHolding.Value, ExpectedEnd); + double coastDistance = TemplateHelper.HorizontalDistanceSq(landingIfCoasting.Value, ExpectedEnd); + double releaseDistance = TemplateHelper.HorizontalDistanceSq(landingIfReleased.Value, ExpectedEnd); + if (coastDistance <= holdDistance && coastDistance <= releaseDistance) + return PrepareJumpAirProfile.Coast; + + return releaseDistance < holdDistance + ? PrepareJumpAirProfile.Release + : PrepareJumpAirProfile.Hold; + } + + private bool ShouldReleaseByRemainingLead(Location pos, PlayerPhysics physics) + { + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + double forwardSpeed = Math.Max( + 0.0, + TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, _segment.HeadingX, _segment.HeadingZ)); + double dropHeight = Math.Max(0.0, ExpectedStart.Y - ExpectedEnd.Y); + double releaseLead = 0.14 + (Math.Max(0.0, dropHeight - 1.0) * 0.20); + return remaining <= forwardSpeed + releaseLead; + } + + private static Location? PredictLandingPosition(PlayerPhysics physics, World world, bool holdForward, bool holdSprint) + { + PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics); + var input = new MovementInput + { + Forward = holdForward, + Sprint = holdSprint + }; + + for (int tick = 0; tick < 16; tick++) + { + sim.ApplyInput(input); + sim.Tick(world); + if (sim.OnGround) + return new Location(sim.Position.X, sim.Position.Y, sim.Position.Z); + } + + return null; + } + + private static float YawDifference(float current, float target) + { + float delta = target - current; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs new file mode 100644 index 0000000000..29dbc57a27 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs @@ -0,0 +1,417 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Jump across a gap. Uses a phase-based state machine: + /// Approach -> Jump -> Airborne -> Landing. + /// + /// All parkour jumps use sprint-jumping (vanilla optimal horizontal distance). + /// The key to landing on small platforms is releasing forward/sprint input mid-air + /// once the player is close to or past the target, letting drag decelerate them + /// onto the block. + /// + /// During Approach, the template waits for the yaw to be within 5 degrees of + /// the target direction before jumping. For medium/long jumps, it also builds + /// momentum by sprinting toward the block edge. + /// + public sealed class SprintJumpTemplate : IActionTemplate + { + private enum Phase { Approach, Airborne, Landing } + + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private readonly double _horizDist; + private int _tickCount; + private Phase _phase = Phase.Approach; + private bool _leftGround; + private bool _carriedGroundEntry; + private bool _releaseForwardLatched; + private readonly SidewallParkourController? _sidewallController; + + private const float YawToleranceDeg = 5f; + + public SprintJumpTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + double dx = segment.End.X - segment.Start.X; + double dz = segment.End.Z - segment.Start.Z; + _horizDist = Math.Sqrt(dx * dx + dz * dz); + + if (segment.ParkourProfile == ParkourProfile.Sidewall) + _sidewallController = new SidewallParkourController(segment, nextSegment); + } + + public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) + { + _tickCount++; + + if (_sidewallController is not null) + return _sidewallController.Tick(pos, physics, input, world); + + double dx = ExpectedEnd.X - pos.X; + double dz = ExpectedEnd.Z - pos.Z; + double dy = ExpectedEnd.Y - pos.Y; + double horizDistSq = dx * dx + dz * dz; + bool prepareJumpTouchdown = _phase == Phase.Airborne && _leftGround && physics.OnGround; + bool groundedPrepareJumpHandoff = (_phase == Phase.Landing || prepareJumpTouchdown) + && physics.OnGround + && _segment.ExitTransition == PathTransitionType.PrepareJump + && _segment.ExitHints.RequireJumpReady + && (TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End) + || TemplateHelper.HasReachedSegmentEndPlane(pos, _segment)); + + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Yaw = groundedPrepareJumpHandoff + ? TemplateHelper.SmoothYaw(physics.Yaw, TemplateHelper.GetExitHeadingYaw(_segment)) + : TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); + + switch (_phase) + { + case Phase.Approach: + if (physics.OnGround) + { + if (_tickCount == 1 && TemplateHelper.GetHorizontalSpeed(physics) > 0.02) + _carriedGroundEntry = true; + + double approachProgress = ((pos.X - ExpectedStart.X) * _segment.HeadingX) + + ((pos.Z - ExpectedStart.Z) * _segment.HeadingZ); + float yawDelta = YawDifference(physics.Yaw, targetYaw); + bool turnInPlace = yawDelta > 35f; + input.Forward = !turnInPlace; + input.Sprint = !turnInPlace; + + bool carriedShortFinalStopJump = _carriedGroundEntry + && _segment.ExitTransition == PathTransitionType.FinalStop + && _horizDist <= 2.5; + bool carriedDescendingFinalStopJump = _carriedGroundEntry + && _segment.ExitTransition == PathTransitionType.FinalStop + && ExpectedEnd.Y < ExpectedStart.Y + && _horizDist <= 3.5; + bool carriedDescendingParkourJump = _carriedGroundEntry + && _segment.ExitTransition == PathTransitionType.PrepareJump + && ExpectedEnd.Y < ExpectedStart.Y + && _horizDist <= 3.5; + if (carriedShortFinalStopJump || carriedDescendingFinalStopJump || carriedDescendingParkourJump) + input.Sprint = false; + + // Build momentum before jumping. Sprint speed is ~5.6 m/s + // (0.28 blocks/tick). More run-up = more airtime distance. + // Standing sprint jump (0t): ~3.6 blocks horizontal + // 2-tick sprint (0.56m): ~4.3 blocks horizontal + // 4-tick sprint (1.1m): ~5.0 blocks horizontal + double minApproachDistance; + bool carriedLongDescendingJump = _carriedGroundEntry + && ExpectedEnd.Y < ExpectedStart.Y + && _horizDist >= 5.0; + if (carriedLongDescendingJump) + minApproachDistance = 0.8; // use nearly the full landing block to preserve long-jump carry + else if (_horizDist >= 5.0) + minApproachDistance = 0.8; // 3+ ticks of sprint + else if (_horizDist >= 4.0) + minApproachDistance = 0.6; // 2-3 ticks of sprint + else if (_horizDist > 3.5) + minApproachDistance = 0.3; // 1-2 ticks of sprint + else + minApproachDistance = 0.0; + + bool posReady = approachProgress >= minApproachDistance; + // Baritone snaps rotation to the exact target every tick and + // the server accepts it without disconnection. For our smooth + // yaw model we only need it at the takeoff tick: snap yaw the + // moment we are positionally ready, so the jump vector is + // aligned regardless of how many ticks we had to turn. This + // prevents mis-jumps when the approach was short and the + // 35 deg/tick smoothing had not finished by the jump tick. + if (posReady) + { + physics.Yaw = targetYaw; + input.Jump = true; + _phase = Phase.Airborne; + } + } + else + { + float yawDelta = YawDifference(physics.Yaw, targetYaw); + bool turnInPlace = yawDelta > 35f; + input.Forward = !turnInPlace; + input.Sprint = !turnInPlace; + } + // Widen the approach/run-up budget. With snap-to-target yaw the + // player aligns in a single tick, but servers may still need a few + // extra ticks of sprint acceleration on long jumps. + if (_tickCount > 80) + return TemplateState.Failed; + break; + + case Phase.Airborne: + { + if (!physics.OnGround) + _leftGround = true; + + bool pastTarget = IsPastTarget(pos); + bool parkourOnOrPastTarget = _segment.MoveType == MoveType.Parkour + && (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd) + || TemplateHelper.HasReachedSegmentEndPlane(pos, _segment) + || pastTarget); + bool biasTowardExitInAir = _segment.ExitTransition == PathTransitionType.LandingRecovery + ? TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment, distanceThreshold: 1.5) + : TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment); + if (parkourOnOrPastTarget || biasTowardExitInAir) + TemplateHelper.FaceExitHeading(physics, _segment); + + bool lookaheadAirBrake = TransitionBrakingPlanner.ShouldReleaseForwardInAir( + _segment, _nextSegment, pos, physics, world); + bool releaseInAir = ShouldReleaseInAir(pos, physics, world); + _releaseForwardLatched |= releaseInAir; + bool earlySoftBrake = _segment.ExitTransition == PathTransitionType.LandingRecovery + && lookaheadAirBrake + && !releaseInAir; + + if (_releaseForwardLatched || pastTarget) + { + input.Forward = false; + input.Sprint = false; + } + else if (earlySoftBrake) + { + input.Forward = true; + input.Sprint = false; + } + else + { + input.Forward = true; + input.Sprint = !(_segment.ExitTransition == PathTransitionType.FinalStop + && ExpectedEnd.Y < ExpectedStart.Y); + } + + if (_leftGround && physics.OnGround) + { + _phase = Phase.Landing; + goto case Phase.Landing; + } + break; + } + + case Phase.Landing: + bool descendingPrepareJump = _segment.ExitTransition == PathTransitionType.PrepareJump + && ExpectedEnd.Y < ExpectedStart.Y; + bool descendingPrepareJumpOnSupport = descendingPrepareJump + && physics.OnGround + && TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, _segment.End); + bool descendingPrepareJumpPastSupport = descendingPrepareJump + && physics.OnGround + && TemplateHelper.HasReachedSegmentEndPlane(pos, _segment) + && !descendingPrepareJumpOnSupport; + + if (descendingPrepareJumpPastSupport) + { + input.Forward = false; + input.Sprint = false; + input.Back = true; + TemplateHelper.FaceExitHeading(physics, _segment); + } + else + { + 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.PrepareJump + && physics.OnGround + && (!descendingPrepareJump || descendingPrepareJumpOnSupport) + && GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + { + return TemplateState.Complete; + } + + double horizToleranceLinear = _horizDist >= 3.5 ? 1.5 : 1.0; + double horizToleranceSq = horizToleranceLinear * horizToleranceLinear; + double vertTolerance = Math.Abs(ExpectedEnd.Y - ExpectedStart.Y) > 0.5 ? 1.5 : 1.0; + if (_segment.ExitTransition == PathTransitionType.ContinueStraight + && horizDistSq < horizToleranceSq && Math.Abs(dy) < vertTolerance) + return TemplateState.Complete; + + if (_segment.ExitTransition != PathTransitionType.ContinueStraight + && physics.OnGround + && (TemplateHelper.IsSettledOnTargetBlock(pos, ExpectedEnd, physics) + || IsSettledOnTurnEntryStrip(pos, physics))) + { + return TemplateState.Complete; + } + + // Baritone-style lenient success: the moment the player is standing + // on the target block (floor center matches), the jump is done. We + // keep the stricter settle checks above as the primary path because + // they capture momentum continuity for downstream segments, but the + // lenient check prevents spurious failures when the player touches + // down slightly off-center or with a small residual slide. + if (_segment.ExitTransition != PathTransitionType.ContinueStraight + && physics.OnGround + && LandedInsideTargetBlock(pos)) + { + return TemplateState.Complete; + } + break; + } + + if (pos.Y < ExpectedEnd.Y - 4.0) + return TemplateState.Failed; + + // Baritone's MAX_TICKS_AWAY is 200 ticks (10 seconds) before it gives up + // on a single movement. Short 60-tick windows were too tight for jumps + // that include a run-up, a long airtime, and landing drag settling. + if (_tickCount > 200) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + private bool LandedInsideTargetBlock(Location pos) + { + if (!TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd)) + return false; + + // Floor Y must match the target block. Accept anywhere within the block. + return Math.Floor(pos.Y + 1.0E-4) == Math.Floor(ExpectedEnd.Y + 1.0E-4); + } + + private bool IsPastTarget(Location pos) + { + double dirX = ExpectedEnd.X - ExpectedStart.X; + double dirZ = ExpectedEnd.Z - ExpectedStart.Z; + double len = Math.Sqrt(dirX * dirX + dirZ * dirZ); + if (len < 0.001) return false; + dirX /= len; + dirZ /= len; + + double relX = pos.X - ExpectedEnd.X; + double relZ = pos.Z - ExpectedEnd.Z; + double dot = relX * dirX + relZ * dirZ; + return dot > 0.0; + } + + private double GetLateralOffsetFromSegmentLine(Location pos) + { + double dirX = ExpectedEnd.X - ExpectedStart.X; + double dirZ = ExpectedEnd.Z - ExpectedStart.Z; + double len = Math.Sqrt(dirX * dirX + dirZ * dirZ); + if (len < 0.001) + return 0.0; + + dirX /= len; + dirZ /= len; + + double relX = pos.X - ExpectedStart.X; + double relZ = pos.Z - ExpectedStart.Z; + return Math.Abs((-dirZ * relX) + (dirX * relZ)); + } + + private bool ShouldReleaseInAir(Location pos, PlayerPhysics physics, World world) + { + if (_segment.ExitTransition == PathTransitionType.ContinueStraight || physics.OnGround) + return false; + + bool heuristicFinalStopRelease = _segment.ExitTransition == PathTransitionType.FinalStop + && ShouldReleaseByRemainingLead(pos, physics); + if (heuristicFinalStopRelease) + return true; + + bool heuristicDescendingPrepareJumpRelease = _segment.ExitTransition == PathTransitionType.PrepareJump + && ExpectedEnd.Y < ExpectedStart.Y + && ShouldReleaseByRemainingLead(pos, physics); + if (heuristicDescendingPrepareJumpRelease) + return true; + + bool plannerWantsRelease = TransitionBrakingPlanner.ShouldReleaseForwardInAir( + _segment, _nextSegment, pos, physics, world); + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + bool centeredOverLandingBlock = remaining <= 1.2; + if (plannerWantsRelease && centeredOverLandingBlock) + return true; + + Location? landingIfHolding = PredictLandingPosition(physics, world, holdForward: true, holdSprint: true); + Location? landingIfReleased = PredictLandingPosition(physics, world, holdForward: false, holdSprint: false); + if (landingIfHolding is null || landingIfReleased is null) + return false; + + bool holdingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfHolding.Value, ExpectedEnd); + bool releasingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfReleased.Value, ExpectedEnd); + + if (plannerWantsRelease && releasingStaysInside) + { + return true; + } + + return !holdingStaysInside && releasingStaysInside; + } + + private bool ShouldReleaseByRemainingLead(Location pos, PlayerPhysics physics) + { + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment); + double forwardSpeed = Math.Max(0.0, + TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, _segment.HeadingX, _segment.HeadingZ)); + double dropHeight = Math.Max(0.0, ExpectedStart.Y - ExpectedEnd.Y); + double releaseLead = 0.14 + (Math.Max(0.0, dropHeight - 1.0) * 0.20); + return remaining <= forwardSpeed + releaseLead; + } + + private Location? PredictLandingPosition(PlayerPhysics physics, World world, bool holdForward, bool holdSprint) + { + PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics); + var input = new MovementInput + { + Forward = holdForward, + Sprint = holdSprint + }; + + for (int tick = 0; tick < 16; tick++) + { + sim.ApplyInput(input); + sim.Tick(world); + if (sim.OnGround) + return new Location(sim.Position.X, sim.Position.Y, sim.Position.Z); + } + + return null; + } + + private bool IsSettledOnTurnEntryStrip(Location pos, PlayerPhysics physics) + { + if (_segment.ExitTransition != PathTransitionType.LandingRecovery || _nextSegment is null) + return false; + + if (_segment.HeadingX == _nextSegment.HeadingX && _segment.HeadingZ == _nextSegment.HeadingZ) + return false; + + double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z; + return TemplateFootingHelper.IsCenterInsideSupportStrip(pos, ExpectedEnd, _nextSegment.End) + && !TemplateFootingHelper.WillCenterLeaveSupportStripNextTick(pos, physics, ExpectedEnd, _nextSegment.End) + && horizontalSpeedSq <= 0.0016; + } + + private static float YawDifference(float current, float target) + { + float delta = target - current; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs b/MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs new file mode 100644 index 0000000000..7d86ce2edb --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs @@ -0,0 +1,139 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + public static class TemplateFootingHelper + { + private const double HalfWidth = PhysicsConsts.PlayerWidth / 2.0; + + public static bool IsFootprintInsideTargetBlock(Location pos, Location target, double epsilon = 1.0E-4) + { + double minX = pos.X - HalfWidth; + double maxX = pos.X + HalfWidth; + double minZ = pos.Z - HalfWidth; + double maxZ = pos.Z + HalfWidth; + + double blockMinX = Math.Floor(target.X); + double blockMaxX = blockMinX + 1.0; + double blockMinZ = Math.Floor(target.Z); + double blockMaxZ = blockMinZ + 1.0; + + return minX >= blockMinX - epsilon + && maxX <= blockMaxX + epsilon + && minZ >= blockMinZ - epsilon + && maxZ <= blockMaxZ + epsilon; + } + + public static bool WillLeaveTargetBlockNextTick(Location pos, PlayerPhysics physics, Location target, double epsilon = 1.0E-4) + { + Location nextPos = new( + pos.X + physics.DeltaMovement.X, + pos.Y, + pos.Z + physics.DeltaMovement.Z); + return !IsFootprintInsideTargetBlock(nextPos, target, epsilon); + } + + public static bool IsCenterInsideTargetBlock(Location pos, Location target, double epsilon = 1.0E-4) + { + double blockMinX = Math.Floor(target.X); + double blockMaxX = blockMinX + 1.0; + double blockMinZ = Math.Floor(target.Z); + double blockMaxZ = blockMinZ + 1.0; + + return pos.X >= blockMinX - epsilon + && pos.X <= blockMaxX + epsilon + && pos.Z >= blockMinZ - epsilon + && pos.Z <= blockMaxZ + epsilon; + } + + public static bool WillCenterLeaveTargetBlockNextTick(Location pos, PlayerPhysics physics, Location target, double epsilon = 1.0E-4) + { + Location nextPos = new( + pos.X + physics.DeltaMovement.X, + pos.Y, + pos.Z + physics.DeltaMovement.Z); + return !IsCenterInsideTargetBlock(nextPos, target, epsilon); + } + + public static bool IsFootprintInsideSupportStrip(Location pos, Location first, Location second, double epsilon = 1.0E-4) + { + double minX = pos.X - HalfWidth; + double maxX = pos.X + HalfWidth; + double minZ = pos.Z - HalfWidth; + double maxZ = pos.Z + HalfWidth; + + GetSupportStripBounds(first, second, out double stripMinX, out double stripMaxX, out double stripMinZ, out double stripMaxZ); + + return minX >= stripMinX - epsilon + && maxX <= stripMaxX + epsilon + && minZ >= stripMinZ - epsilon + && maxZ <= stripMaxZ + epsilon; + } + + public static bool WillLeaveSupportStripNextTick(Location pos, PlayerPhysics physics, Location first, Location second, double epsilon = 1.0E-4) + { + Location nextPos = new( + pos.X + physics.DeltaMovement.X, + pos.Y, + pos.Z + physics.DeltaMovement.Z); + return !IsFootprintInsideSupportStrip(nextPos, first, second, epsilon); + } + + public static bool IsCenterInsideSupportStrip(Location pos, Location first, Location second, double epsilon = 1.0E-4) + { + GetSupportStripBounds(first, second, out double stripMinX, out double stripMaxX, out double stripMinZ, out double stripMaxZ); + + return pos.X >= stripMinX - epsilon + && pos.X <= stripMaxX + epsilon + && pos.Z >= stripMinZ - epsilon + && pos.Z <= stripMaxZ + epsilon; + } + + public static bool WillCenterLeaveSupportStripNextTick(Location pos, PlayerPhysics physics, Location first, Location second, double epsilon = 1.0E-4) + { + Location nextPos = new( + pos.X + physics.DeltaMovement.X, + pos.Y, + pos.Z + physics.DeltaMovement.Z); + return !IsCenterInsideSupportStrip(nextPos, first, second, epsilon); + } + + public static bool WillCrossSupportExitNextTick(Location pos, PlayerPhysics physics, PathSegment segment, double epsilon = 1.0E-4) + { + double nextX = pos.X + physics.DeltaMovement.X; + double nextZ = pos.Z + physics.DeltaMovement.Z; + + double blockMinX = Math.Floor(segment.End.X); + double blockMaxX = blockMinX + 1.0; + double blockMinZ = Math.Floor(segment.End.Z); + double blockMaxZ = blockMinZ + 1.0; + + if (segment.HeadingX > 0 && nextX > blockMaxX - HalfWidth + epsilon) + return true; + if (segment.HeadingX < 0 && nextX < blockMinX + HalfWidth - epsilon) + return true; + if (segment.HeadingZ > 0 && nextZ > blockMaxZ - HalfWidth + epsilon) + return true; + if (segment.HeadingZ < 0 && nextZ < blockMinZ + HalfWidth - epsilon) + return true; + + return false; + } + + private static void GetSupportStripBounds(Location first, Location second, + out double minX, out double maxX, out double minZ, out double maxZ) + { + double firstMinX = Math.Floor(first.X); + double secondMinX = Math.Floor(second.X); + double firstMinZ = Math.Floor(first.Z); + double secondMinZ = Math.Floor(second.Z); + + minX = Math.Min(firstMinX, secondMinX); + maxX = Math.Max(firstMinX, secondMinX) + 1.0; + minZ = Math.Min(firstMinZ, secondMinZ); + maxZ = Math.Max(firstMinZ, secondMinZ) + 1.0; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs b/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs new file mode 100644 index 0000000000..b96a7089c6 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs @@ -0,0 +1,407 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + internal static class TemplateHelper + { + private const double EyeHeight = 1.62; + private const float MaxYawStepPerTick = 35f; + private const float MaxPitchStepPerTick = 25f; + + // Callers that want to force an immediate alignment (e.g. right before + // firing a jump so the takeoff vector matches the target) can use these + // "snap" overloads instead of SmoothYaw/SmoothPitch. + internal static float SnapStep => 360f; + + internal static float CalculateYaw(double dx, double dz) + { + float yaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); + if (yaw < 0) yaw += 360; + return yaw; + } + + /// + /// Calculate the pitch angle to look from current eye position toward + /// the target's feet-level Y. dy = targetFeetY - playerFeetY. + /// + internal static float CalculatePitch(double dx, double dy, double dz) + { + double horizDist = Math.Sqrt(dx * dx + dz * dz); + // Look toward the target's eye level, not feet. + // Both player and target are at feet+EyeHeight, so the vertical + // difference is just dy (target feet Y - player feet Y). + float pitch = (float)(-Math.Atan2(dy, horizDist) / Math.PI * 180.0); + return Math.Clamp(pitch, -90f, 90f); + } + + /// + /// Smoothly interpolate yaw toward a target, respecting wrap-around at 0/360. + /// + internal static float SmoothYaw(float current, float target, float maxStep = MaxYawStepPerTick) + { + float delta = target - current; + // Normalize to [-180, 180] + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + + if (Math.Abs(delta) <= maxStep) + return target; + + float result = current + Math.Sign(delta) * maxStep; + if (result < 0) result += 360f; + if (result >= 360f) result -= 360f; + return result; + } + + /// + /// Smoothly interpolate pitch toward a target. + /// + internal static float SmoothPitch(float current, float target, float maxStep = MaxPitchStepPerTick) + { + float delta = target - current; + if (Math.Abs(delta) <= maxStep) + return target; + return current + Math.Sign(delta) * maxStep; + } + + internal static double HorizontalDistanceSq(Location a, Location b) + { + double dx = a.X - b.X; + double dz = a.Z - b.Z; + return dx * dx + dz * dz; + } + + internal static bool IsNear(Location pos, Location target, + double horizThresholdSq = 0.25, double vertThreshold = 0.8) + { + double dx = target.X - pos.X; + double dz = target.Z - pos.Z; + double dy = target.Y - pos.Y; + return dx * dx + dz * dz < horizThresholdSq && Math.Abs(dy) < vertThreshold; + } + + internal static void FaceSegmentHeading(PlayerPhysics physics, PathSegment segment) + { + float headingYaw = CalculateYaw(segment.HeadingX, segment.HeadingZ); + physics.Yaw = SmoothYaw(physics.Yaw, headingYaw); + } + + internal static void FaceExitHeading(PlayerPhysics physics, PathSegment segment) + { + float headingYaw = GetExitHeadingYaw(segment); + physics.Yaw = SmoothYaw(physics.Yaw, headingYaw); + } + + internal static void FaceExitHeading(PlayerPhysics physics, PathSegment segment, PathSegment? nextSegment) + { + float headingYaw = GetExitHeadingYaw(segment, nextSegment); + physics.Yaw = SmoothYaw(physics.Yaw, headingYaw); + } + + internal static void ApplyDecision(MovementInput input, TransitionBrakingDecision decision) + { + input.Forward = decision.HoldForward; + input.Sprint = decision.HoldSprint; + input.Back = decision.HoldBack; + } + + internal static bool HasReachedSegmentEndPlane(Location pos, PathSegment segment, double tolerance = 0.05) + { + GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ); + double relX = pos.X - segment.End.X; + double relZ = pos.Z - segment.End.Z; + return relX * dirX + relZ * dirZ >= -tolerance; + } + + internal static double ProjectHorizontalSpeedAlongSegment(PlayerPhysics physics, PathSegment segment) + { + GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ); + return physics.DeltaMovement.X * dirX + physics.DeltaMovement.Z * dirZ; + } + + internal static double ProjectHorizontalSpeedAlongHint(PlayerPhysics physics, PathSegment segment) + { + GetExitHeading(segment, out int headingX, out int headingZ); + return ProjectHorizontalSpeedAlongHeading(physics, headingX, headingZ); + } + + internal static double ProjectHorizontalSpeedAlongHeading(PlayerPhysics physics, int headingX, int headingZ) + { + if (headingX == 0 && headingZ == 0) + return GetHorizontalSpeed(physics); + + return physics.DeltaMovement.X * headingX + physics.DeltaMovement.Z * headingZ; + } + + internal static double GetHorizontalSpeed(PlayerPhysics physics) + { + return Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + } + + internal static double RemainingDistanceAlongSegment(Location pos, PathSegment segment) + { + double dx = segment.End.X - pos.X; + double dz = segment.End.Z - pos.Z; + return dx * segment.HeadingX + dz * segment.HeadingZ; + } + + internal static void GetApproachHeading(PathSegment segment, out int headingX, out int headingZ) + { + if (segment.ParkourProfile == ParkourProfile.Sidewall) + { + double dx = Math.Abs(segment.End.X - segment.Start.X); + double dz = Math.Abs(segment.End.Z - segment.Start.Z); + + if (dx > dz) + { + headingX = segment.HeadingX; + headingZ = 0; + } + else + { + headingX = 0; + headingZ = segment.HeadingZ; + } + + if (headingX != 0 || headingZ != 0) + return; + } + + headingX = segment.HeadingX; + headingZ = segment.HeadingZ; + } + + internal static float GetApproachYaw(PathSegment segment) + { + GetApproachHeading(segment, out int headingX, out int headingZ); + return CalculateYaw(headingX, headingZ); + } + + internal static double ProgressAlongApproach(Location pos, PathSegment segment) + { + GetApproachHeading(segment, out int headingX, out int headingZ); + return ((pos.X - segment.Start.X) * headingX) + ((pos.Z - segment.Start.Z) * headingZ); + } + + internal static float GetSidewallTakeoffYaw(PathSegment segment) + { + float approachYaw = GetApproachYaw(segment); + float targetYaw = CalculateYaw(segment.End.X - segment.Start.X, segment.End.Z - segment.Start.Z); + + double major = Math.Max(Math.Abs(segment.End.X - segment.Start.X), Math.Abs(segment.End.Z - segment.Start.Z)); + float blend = major switch + { + <= 2.0 => 0.55f, + <= 3.0 => 0.52f, + <= 4.0 => 0.50f, + _ => 0.48f + }; + + if (major >= 4.0) + blend += 0.04f; + + if (segment.End.Y > segment.Start.Y) + blend = Math.Max(0.40f, blend - 0.06f); + else if (segment.End.Y < segment.Start.Y) + { + blend = Math.Min(0.62f, blend + 0.06f); + if (major <= 2.0) + blend = Math.Min(0.66f, blend + 0.05f); + } + + return InterpolateYaw(approachYaw, targetYaw, blend); + } + + internal static double LateralOffsetFromSegmentLine(Location pos, PathSegment segment) + { + GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ); + if (dirX == 0.0 && dirZ == 0.0) + return 0.0; + + double relX = pos.X - segment.Start.X; + double relZ = pos.Z - segment.Start.Z; + return Math.Abs((-dirZ * relX) + (dirX * relZ)); + } + + internal static bool ShouldBiasTowardExitHeading(Location pos, PathSegment segment, double distanceThreshold = 0.35) + { + GetExitHeading(segment, out int headingX, out int headingZ); + if ((headingX == 0 && headingZ == 0) + || (headingX == segment.HeadingX && headingZ == segment.HeadingZ)) + { + return false; + } + + return RemainingDistanceAlongSegment(pos, segment) <= distanceThreshold; + } + + internal static bool ShouldBiasTowardExitHeading(Location pos, PathSegment segment, PathSegment? nextSegment, double distanceThreshold = 0.35) + { + GetExitHeading(segment, nextSegment, out int headingX, out int headingZ); + if ((headingX == 0 && headingZ == 0) + || (headingX == segment.HeadingX && headingZ == segment.HeadingZ)) + { + return false; + } + + return RemainingDistanceAlongSegment(pos, segment) <= distanceThreshold; + } + + internal static bool IsSettledOnTargetBlock(Location pos, Location target, PlayerPhysics physics, + double speedThresholdSq = 0.0016) + { + double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z; + return TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, target) + && !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, target) + && horizontalSpeedSq <= speedThresholdSq; + } + + internal static bool IsSettledAtEnd(Location pos, Location target, PlayerPhysics physics, + double horizThresholdSq = 0.0025, double speedThresholdSq = 0.0016) + { + if (IsSettledOnTargetBlock(pos, target, physics, speedThresholdSq)) + return true; + + double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z; + if (horizontalSpeedSq > speedThresholdSq) + return false; + + if (TemplateFootingHelper.IsCenterInsideTargetBlock(pos, target) + && !TemplateFootingHelper.WillCenterLeaveTargetBlockNextTick(pos, physics, target)) + { + return true; + } + + double dx = target.X - pos.X; + double dz = target.Z - pos.Z; + return dx * dx + dz * dz <= horizThresholdSq; + } + + internal static double HeadingPenaltyDegrees(float yaw, PathSegment segment) + { + GetExitHeading(segment, out int headingX, out int headingZ); + return HeadingPenaltyDegrees(yaw, headingX, headingZ); + } + + internal static bool ShouldTurnInPlaceBeforeAdvancing(float yaw, PathSegment segment, double maxAdvanceHeadingPenalty = 35.0) + { + return HeadingPenaltyDegrees(yaw, segment) > maxAdvanceHeadingPenalty; + } + + internal static double HeadingPenaltyDegrees(float yaw, int headingX, int headingZ) + { + if (headingX == 0 && headingZ == 0) + return 0.0; + + float targetYaw = CalculateYaw(headingX, headingZ); + float delta = targetYaw - yaw; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + + internal static float GetExitHeadingYaw(PathSegment segment) + { + GetExitHeading(segment, out int headingX, out int headingZ); + return CalculateYaw(headingX, headingZ); + } + + internal static float GetExitHeadingYaw(PathSegment segment, PathSegment? nextSegment) + { + GetExitHeading(segment, nextSegment, out int headingX, out int headingZ); + return CalculateYaw(headingX, headingZ); + } + + internal static void GetExitHeading(PathSegment segment, out int headingX, out int headingZ) + { + headingX = segment.ExitHints.DesiredHeadingX; + headingZ = segment.ExitHints.DesiredHeadingZ; + + if (headingX == 0 && headingZ == 0) + { + headingX = segment.HeadingX; + headingZ = segment.HeadingZ; + } + } + + internal static void GetExitHeading(PathSegment segment, PathSegment? nextSegment, out int headingX, out int headingZ) + { + if (nextSegment is not null + && nextSegment.MoveType == MoveType.Parkour + && nextSegment.ParkourProfile == ParkourProfile.Sidewall) + { + GetApproachHeading(nextSegment, out headingX, out headingZ); + if (headingX != 0 || headingZ != 0) + return; + } + + GetExitHeading(segment, out headingX, out headingZ); + } + + internal static PlayerPhysics ClonePhysicsForPlanning(PlayerPhysics physics) + { + return new PlayerPhysics + { + Position = physics.Position, + DeltaMovement = physics.DeltaMovement, + Yaw = physics.Yaw, + Pitch = physics.Pitch, + OnGround = physics.OnGround, + HorizontalCollision = physics.HorizontalCollision, + VerticalCollision = physics.VerticalCollision, + VerticalCollisionBelow = physics.VerticalCollisionBelow, + FallDistance = physics.FallDistance, + StuckSpeedMultiplier = physics.StuckSpeedMultiplier, + Xxa = physics.Xxa, + Zza = physics.Zza, + Yya = physics.Yya, + Jumping = physics.Jumping, + Sprinting = physics.Sprinting, + Sneaking = physics.Sneaking, + CreativeFlying = physics.CreativeFlying, + InWater = physics.InWater, + IsUnderWater = physics.IsUnderWater, + InLava = physics.InLava, + OnClimbable = physics.OnClimbable, + HasSlowFalling = physics.HasSlowFalling, + HasLevitation = physics.HasLevitation, + LevitationAmplifier = physics.LevitationAmplifier, + MovementSpeed = physics.MovementSpeed + }; + } + + private static void GetNormalizedSegmentDirection(PathSegment segment, out double dirX, out double dirZ) + { + dirX = segment.End.X - segment.Start.X; + dirZ = segment.End.Z - segment.Start.Z; + double len = Math.Sqrt(dirX * dirX + dirZ * dirZ); + if (len < 1.0E-6) + { + dirX = 0.0; + dirZ = 0.0; + return; + } + + dirX /= len; + dirZ /= len; + } + + private static float InterpolateYaw(float from, float to, float factor) + { + float delta = to - from; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + + float result = from + (delta * factor); + while (result < 0f) result += 360f; + while (result >= 360f) result -= 360f; + return result; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs new file mode 100644 index 0000000000..1bb1e7a206 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs @@ -0,0 +1,107 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + /// + /// Walk/sprint toward a destination on the same Y level. + /// Used for Traverse and Diagonal moves. + /// + public sealed class WalkTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private Location _lastPos; + private int _stuckTicks; + private int _airborneTicks; + + public WalkTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + _lastPos = segment.Start; + } + + 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; + // While approaching the end, steer via pos->end so lateral drift self- + // corrects. Once the center has entered the target block the pos->end + // vector becomes tiny/negative and flips yaw by ~180 degrees, which + // fights GroundedSegmentController's exit-heading rotation and locks + // yaw at a local equilibrium (e.g. 333 deg on a 1,1 diagonal) where + // HeadingPenalty never drops below the 8 deg ShouldComplete gate. + // Fall back to the stable quantized segment heading once inside the + // target block so the completion check and exit rotation converge. + // Skip the exit-heading bias on stable-footing Turn exits: it + // rotates yaw mid-segment while the bot still has along-segment + // momentum, which on a 1-block walkway drifts the bot + // perpendicular and walks it off the edge. The next segment's + // template snaps yaw on its first tick, so nothing is lost by + // deferring the rotation. Keep the bias when the next segment + // is a jump (RequireJumpReady): we need yaw aligned before + // takeoff or the jump direction will be off. + bool suppressBiasForSafeTurn = _segment.ExitTransition == PathTransitionType.Turn + && _segment.ExitHints.RequireStableFooting + && !_segment.ExitHints.RequireJumpReady; + float targetYaw; + if (!suppressBiasForSafeTurn + && TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment)) + targetYaw = TemplateHelper.GetExitHeadingYaw(_segment); + else if (TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End)) + targetYaw = TemplateHelper.CalculateYaw(_segment.HeadingX, _segment.HeadingZ); + else + targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + // Snap yaw on the first tick so we don't push forward input while the + // bot is still rotating from whatever yaw it had before this segment + // started (e.g. a random post-teleport orientation). Baritone-style: + // the server accepts instant yaw updates and the narrow 1-block lanes + // in parkour courses don't tolerate 3 ticks of sideways drift. + physics.Yaw = _tickCount == 1 + ? targetYaw + : TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); + + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + + double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos); + _stuckTicks = movedSq < 0.0005 ? _stuckTicks + 1 : 0; + _lastPos = pos; + + // Walk/Diagonal is a grounded move: if the bot is airborne for more + // than a handful of ticks the platform is gone beneath us (e.g. we + // rotated toward an exit heading on a narrow 1-block walkway and + // stepped off the edge). Fail fast so the replanner can recover + // before gravity carries the bot 10+ blocks out of position. + _airborneTicks = physics.OnGround ? 0 : _airborneTicks + 1; + if (_airborneTicks > 8) + return TemplateState.Failed; + + int maxTicks = _segment.ExitTransition switch + { + PathTransitionType.ContinueStraight => 100, + PathTransitionType.PrepareJump => 80, + _ => 140 + }; + if (_stuckTicks > 40 || _tickCount > maxTicks) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs b/MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs new file mode 100644 index 0000000000..a51e8b6a19 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs @@ -0,0 +1,14 @@ +namespace MinecraftClient.Pathing.Execution +{ + public readonly record struct TransitionBrakingDecision(bool HoldForward, bool HoldSprint, bool HoldBack) + { + public static TransitionBrakingDecision CarryMomentum(bool preserveSprint) => + new(true, preserveSprint, false); + + public static TransitionBrakingDecision Coast => + new(false, false, false); + + public static TransitionBrakingDecision Brake => + new(false, false, true); + } +} diff --git a/MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs b/MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs new file mode 100644 index 0000000000..a624289c15 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs @@ -0,0 +1,148 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public static class TransitionBrakingPlanner + { + private const double GroundSpeedThreshold = 0.025; + private const int MaxSimulationTicks = 14; + private const double FinalStopLead = 0.06; + private const double FinalBrakeLead = 0.04; + private const double TurnBrakeLead = 0.10; + private const double AirReleaseLead = 0.14; + + public static TransitionBrakingDecision Plan(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) + { + if (physics.OnGround + && current.ExitTransition == PathTransitionType.LandingRecovery + && next is not null + && !HasSameHeading(current, next)) + { + double remaining = RemainingDistanceAlongSegment(current, pos); + double forwardSpeed = Math.Max(0.0, + ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ)); + double maxExitSpeed = !double.IsPositiveInfinity(current.ExitHints.MaxExitSpeed) + ? current.ExitHints.MaxExitSpeed + : 0.035; + double coastStopDistance = EstimateGroundStopDistance( + physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: false); + double hardBrakeDistance = EstimateGroundStopDistance( + physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: true); + + if (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, current.End) + && forwardSpeed <= maxExitSpeed) + { + return TransitionBrakingDecision.Coast; + } + + if (remaining < 0.0 && forwardSpeed > maxExitSpeed) + return TransitionBrakingDecision.Brake; + + if (forwardSpeed > GroundSpeedThreshold && remaining <= hardBrakeDistance + TurnBrakeLead) + return TransitionBrakingDecision.Brake; + + if (remaining <= coastStopDistance + FinalStopLead) + return TransitionBrakingDecision.Coast; + } + + TransitionInputProfile profile; + if (physics.OnGround) + { + profile = TransitionLookaheadEvaluator.ChooseGroundProfile(current, pos, physics, world); + } + else + { + if (!current.ExitHints.AllowAirBrake) + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); + + profile = TransitionLookaheadEvaluator.ChooseAirProfile(current, pos, physics, world); + } + + return profile switch + { + TransitionInputProfile.Carry => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint), + TransitionInputProfile.Coast => TransitionBrakingDecision.Coast, + TransitionInputProfile.Brake => TransitionBrakingDecision.Brake, + TransitionInputProfile.AirHoldForward => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint), + TransitionInputProfile.AirRelease => TransitionBrakingDecision.Coast, + TransitionInputProfile.AirBrake => TransitionBrakingDecision.Brake, + _ => TransitionBrakingDecision.Coast + }; + } + + public static bool ShouldReleaseForwardInAir(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics) + { + if (current.ExitTransition is not (PathTransitionType.FinalStop or PathTransitionType.Turn or PathTransitionType.LandingRecovery)) + return false; + + double remaining = RemainingDistanceAlongSegment(current, pos); + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ)); + + return remaining <= forwardSpeed + AirReleaseLead; + } + + public static bool ShouldReleaseForwardInAir(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) + { + if (!current.ExitHints.AllowAirBrake) + return false; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile(current, pos, physics, world); + return profile is TransitionInputProfile.AirRelease or TransitionInputProfile.AirBrake; + } + + public static double EstimateGroundStopDistance(PlayerPhysics physics, World world, int headingX, int headingZ, bool applyBackBrake) + { + if (!physics.OnGround) + return 0.0; + + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, headingX, headingZ)); + if (forwardSpeed <= GroundSpeedThreshold) + return 0.0; + + float blockFriction = PlayerPhysics.GetMaterialFriction( + world.GetBlock(new Location(physics.Position.X, physics.Position.Y - 0.5000010, physics.Position.Z)).Type); + double drag = blockFriction * PhysicsConsts.FrictionMultiplier; + double acceleration = physics.MovementSpeed + * (PhysicsConsts.GroundAccelerationFactor / (drag * drag * drag)) + * PhysicsConsts.InputFriction; + + if (applyBackBrake) + acceleration *= 0.98; + + double distance = 0.0; + double speed = forwardSpeed; + for (int tick = 0; tick < MaxSimulationTicks; tick++) + { + distance += speed; + speed = applyBackBrake + ? Math.Max(0.0, (speed - acceleration) * drag) + : speed * drag; + + if (speed <= GroundSpeedThreshold) + break; + } + + return distance; + } + + private static double RemainingDistanceAlongSegment(PathSegment current, Location pos) + { + double dx = current.End.X - pos.X; + double dz = current.End.Z - pos.Z; + return dx * current.HeadingX + dz * current.HeadingZ; + } + + private static double ProjectHorizontalSpeedAlongHeading(PlayerPhysics physics, int headingX, int headingZ) + { + return physics.DeltaMovement.X * headingX + physics.DeltaMovement.Z * headingZ; + } + + private static bool HasSameHeading(PathSegment current, PathSegment next) + { + return current.HeadingX == next.HeadingX && current.HeadingZ == next.HeadingZ; + } + } +} diff --git a/MinecraftClient/Pathing/Execution/TransitionInputProfile.cs b/MinecraftClient/Pathing/Execution/TransitionInputProfile.cs new file mode 100644 index 0000000000..2f0853e6e3 --- /dev/null +++ b/MinecraftClient/Pathing/Execution/TransitionInputProfile.cs @@ -0,0 +1,12 @@ +namespace MinecraftClient.Pathing.Execution +{ + public enum TransitionInputProfile + { + Carry, + Coast, + Brake, + AirHoldForward, + AirRelease, + AirBrake + } +} diff --git a/MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs b/MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs new file mode 100644 index 0000000000..d3ad6626df --- /dev/null +++ b/MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs @@ -0,0 +1,196 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public static class TransitionLookaheadEvaluator + { + public static TransitionInputProfile ChooseGroundProfile(PathSegment segment, Location pos, PlayerPhysics physics, World world) + { + double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, segment); + double forwardSpeed = Math.Max(0.0, + TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, segment.HeadingX, segment.HeadingZ)); + + bool requiresJumpEntry = segment.ExitHints.RequireJumpReady + || segment.ExitTransition == PathTransitionType.PrepareJump; + + if (segment.ExitTransition == PathTransitionType.ContinueStraight && !requiresJumpEntry) + return TransitionInputProfile.Carry; + + if (requiresJumpEntry) + return TransitionInputProfile.Carry; + + bool requiresSlowEntry = segment.ExitHints.RequireStableFooting + || segment.ExitTransition is PathTransitionType.FinalStop or PathTransitionType.Turn + || (segment.ExitTransition == PathTransitionType.LandingRecovery + && (segment.ExitHints.AllowAirBrake || IsFiniteSpeedCap(segment))); + + if (!requiresSlowEntry) + return TransitionInputProfile.Carry; + + double maxExitSpeed = GetTargetMaxExitSpeed(segment); + double hardBrakeDistance = TransitionBrakingPlanner.EstimateGroundStopDistance( + physics, world, segment.HeadingX, segment.HeadingZ, applyBackBrake: true); + double coastStopDistance = TransitionBrakingPlanner.EstimateGroundStopDistance( + physics, world, segment.HeadingX, segment.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 segment, Location pos, PlayerPhysics physics, World world) + { + if (!segment.ExitHints.AllowAirBrake) + return TransitionInputProfile.AirHoldForward; + + TransitionInputProfile[] candidates = + [ + TransitionInputProfile.AirHoldForward, + TransitionInputProfile.AirRelease, + TransitionInputProfile.AirBrake + ]; + + return ChooseBest(segment, pos, physics, world, candidates); + } + + private static TransitionInputProfile ChooseBest(PathSegment segment, 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, pos, physics, world, candidate); + if (score < bestScore) + { + best = candidate; + bestScore = score; + } + } + + return best; + } + + private static double Score(PathSegment segment, 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 = 0.0; + double exitSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHint(sim, segment); + double horizontalSpeed = TemplateHelper.GetHorizontalSpeed(sim); + + if (segment.ExitHints.RequireGrounded && !sim.OnGround) + score += 1000.0; + + if (segment.ExitHints.RequireStableFooting + && !TemplateHelper.IsSettledOnTargetBlock(simPos, segment.End, sim)) + { + score += 1000.0; + } + + if (segment.ExitHints.RequireStableFooting && !sim.OnGround) + { + double remaining = TemplateHelper.RemainingDistanceAlongSegment(simPos, segment); + if (remaining > 0.0) + score += 2000.0 + remaining * 500.0; + + if (simPos.Y < segment.End.Y) + score += 2000.0 + (segment.End.Y - simPos.Y) * 500.0; + } + + if (exitSpeed < segment.ExitHints.MinExitSpeed) + score += (segment.ExitHints.MinExitSpeed - exitSpeed) * 200.0; + + if (exitSpeed > segment.ExitHints.MaxExitSpeed) + score += (exitSpeed - segment.ExitHints.MaxExitSpeed) * 200.0; + + score += TemplateHelper.HeadingPenaltyDegrees(sim.Yaw, segment); + + if (segment.ExitHints.RequireStableFooting) + { + double dx = segment.End.X - simPos.X; + double dz = segment.End.Z - simPos.Z; + score += (dx * dx + dz * dz) * 20.0; + } + else + { + score += Math.Abs(TemplateHelper.RemainingDistanceAlongSegment(simPos, segment)) * 10.0; + } + + if (segment.ExitHints.RequireJumpReady && horizontalSpeed < segment.ExitHints.MinExitSpeed) + score += 250.0; + + return score; + } + + private static void ApplyCandidateInput(MovementInput input, TransitionInputProfile candidate, PathSegment segment) + { + switch (candidate) + { + case TransitionInputProfile.Carry: + case TransitionInputProfile.AirHoldForward: + input.Forward = true; + input.Sprint = segment.PreserveSprint || segment.ExitHints.RequireJumpReady; + break; + + case TransitionInputProfile.Brake: + case TransitionInputProfile.AirBrake: + input.Back = true; + break; + + case TransitionInputProfile.Coast: + case TransitionInputProfile.AirRelease: + default: + break; + } + } + + private static bool IsFiniteSpeedCap(PathSegment segment) + { + return !double.IsPositiveInfinity(segment.ExitHints.MaxExitSpeed); + } + + private static double GetTargetMaxExitSpeed(PathSegment segment) + { + if (IsFiniteSpeedCap(segment)) + return segment.ExitHints.MaxExitSpeed; + + return segment.ExitTransition switch + { + PathTransitionType.FinalStop => 0.03, + PathTransitionType.Turn or PathTransitionType.LandingRecovery => 0.035, + _ => double.PositiveInfinity + }; + } + } +} diff --git a/MinecraftClient/Pathing/Goals/GoalBlock.cs b/MinecraftClient/Pathing/Goals/GoalBlock.cs new file mode 100644 index 0000000000..55cbdf7f4e --- /dev/null +++ b/MinecraftClient/Pathing/Goals/GoalBlock.cs @@ -0,0 +1,42 @@ +using System; + +namespace MinecraftClient.Pathing.Goals +{ + public sealed class GoalBlock : IGoal + { + public int X { get; } + public int Y { get; } + public int Z { get; } + + public GoalBlock(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } + + public bool IsInGoal(int x, int y, int z) + => x == X && y == Y && z == Z; + + public double Heuristic(int x, int y, int z) + { + int dx = Math.Abs(x - X); + int dy = Math.Abs(y - Y); + int dz = Math.Abs(z - Z); + return DistanceHeuristic(dx, dy, dz); + } + + internal static double DistanceHeuristic(int dx, int dy, int dz) + { + int horizontal = Math.Max(dx, dz); + int diagonal = Math.Min(dx, dz); + int straight = horizontal - diagonal; + double cost = diagonal * Core.ActionCosts.SprintOneBlock * Core.ActionCosts.DiagonalMultiplier + + straight * Core.ActionCosts.SprintOneBlock + + Math.Abs(dy) * Core.ActionCosts.SprintOneBlock; + return cost; + } + + public override string ToString() => $"GoalBlock({X}, {Y}, {Z})"; + } +} diff --git a/MinecraftClient/Pathing/Goals/GoalComposite.cs b/MinecraftClient/Pathing/Goals/GoalComposite.cs new file mode 100644 index 0000000000..68dd5d505d --- /dev/null +++ b/MinecraftClient/Pathing/Goals/GoalComposite.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace MinecraftClient.Pathing.Goals +{ + public sealed class GoalComposite : IGoal + { + private readonly IGoal[] _goals; + + public GoalComposite(params IGoal[] goals) + { + ArgumentNullException.ThrowIfNull(goals); + _goals = goals; + } + + public GoalComposite(IEnumerable goals) + { + ArgumentNullException.ThrowIfNull(goals); + _goals = goals is IGoal[] arr ? arr : [.. goals]; + } + + public bool IsInGoal(int x, int y, int z) + { + foreach (var g in _goals) + { + if (g.IsInGoal(x, y, z)) + return true; + } + return false; + } + + public double Heuristic(int x, int y, int z) + { + double min = double.MaxValue; + foreach (var g in _goals) + { + double h = g.Heuristic(x, y, z); + if (h < min) min = h; + } + return min; + } + + public override string ToString() => $"GoalComposite({_goals.Length} goals)"; + } +} diff --git a/MinecraftClient/Pathing/Goals/GoalNear.cs b/MinecraftClient/Pathing/Goals/GoalNear.cs new file mode 100644 index 0000000000..1fbd3d210a --- /dev/null +++ b/MinecraftClient/Pathing/Goals/GoalNear.cs @@ -0,0 +1,42 @@ +using System; + +namespace MinecraftClient.Pathing.Goals +{ + public sealed class GoalNear : IGoal + { + public int X { get; } + public int Y { get; } + public int Z { get; } + public int Range { get; } + private readonly int _rangeSq; + + public GoalNear(int x, int y, int z, int range) + { + X = x; + Y = y; + Z = z; + Range = range; + _rangeSq = range * range; + } + + public bool IsInGoal(int x, int y, int z) + { + int dx = x - X; + int dy = y - Y; + int dz = z - Z; + return dx * dx + dy * dy + dz * dz <= _rangeSq; + } + + public double Heuristic(int x, int y, int z) + { + int dx = Math.Abs(x - X); + int dy = Math.Abs(y - Y); + int dz = Math.Abs(z - Z); + double h = GoalBlock.DistanceHeuristic(dx, dy, dz); + double reduction = Range * Core.ActionCosts.SprintOneBlock; + return Math.Max(0, h - reduction); + } + + public override string ToString() => $"GoalNear({X}, {Y}, {Z}, range={Range})"; + } +} diff --git a/MinecraftClient/Pathing/Goals/GoalXZ.cs b/MinecraftClient/Pathing/Goals/GoalXZ.cs new file mode 100644 index 0000000000..22a4873f0f --- /dev/null +++ b/MinecraftClient/Pathing/Goals/GoalXZ.cs @@ -0,0 +1,28 @@ +using System; + +namespace MinecraftClient.Pathing.Goals +{ + public sealed class GoalXZ : IGoal + { + public int X { get; } + public int Z { get; } + + public GoalXZ(int x, int z) + { + X = x; + Z = z; + } + + public bool IsInGoal(int x, int y, int z) + => x == X && z == Z; + + public double Heuristic(int x, int y, int z) + { + int dx = Math.Abs(x - X); + int dz = Math.Abs(z - Z); + return GoalBlock.DistanceHeuristic(dx, 0, dz); + } + + public override string ToString() => $"GoalXZ({X}, {Z})"; + } +} diff --git a/MinecraftClient/Pathing/Goals/IGoal.cs b/MinecraftClient/Pathing/Goals/IGoal.cs new file mode 100644 index 0000000000..13cada064b --- /dev/null +++ b/MinecraftClient/Pathing/Goals/IGoal.cs @@ -0,0 +1,8 @@ +namespace MinecraftClient.Pathing.Goals +{ + public interface IGoal + { + bool IsInGoal(int x, int y, int z); + double Heuristic(int x, int y, int z); + } +} diff --git a/MinecraftClient/Pathing/Moves/IMove.cs b/MinecraftClient/Pathing/Moves/IMove.cs new file mode 100644 index 0000000000..69e9e107f2 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/IMove.cs @@ -0,0 +1,18 @@ +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves +{ + /// + /// Represents one type of movement action for path planning. + /// Each implementation defines its spatial check pattern and cost model. + /// + public interface IMove + { + MoveType Type { get; } + int XOffset { get; } + int ZOffset { get; } + bool DynamicY { get; } + + void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result); + } +} diff --git a/MinecraftClient/Pathing/Moves/IMoveExpander.cs b/MinecraftClient/Pathing/Moves/IMoveExpander.cs new file mode 100644 index 0000000000..1d71aad2c8 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/IMoveExpander.cs @@ -0,0 +1,53 @@ +using System; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves; + +/// +/// A feasible neighbor emitted by an . Carries the +/// per-node data the A* main loop needs to update its open set (destination, +/// cost, parkour profile, move type). +/// +public readonly struct MoveNeighbor +{ + public readonly int DestX; + public readonly int DestY; + public readonly int DestZ; + public readonly double Cost; + public readonly ParkourProfile ParkourProfile; + public readonly MoveType MoveType; + + public MoveNeighbor(in MoveResult result, MoveType moveType) + { + DestX = result.DestX; + DestY = result.DestY; + DestZ = result.DestZ; + Cost = result.Cost; + ParkourProfile = result.ParkourProfile; + MoveType = moveType; + } +} + +/// +/// Emits feasible neighbors from a given node. Replaces the old +/// "iterate every pre-instantiated IMove" pattern: A* asks each expander to +/// fill a stack-allocated buffer with all feasible neighbors, and the +/// expander can prune whole categories (e.g. skip Sidewall when no wall is +/// near) without instantiating per-direction IMove objects. +/// +public interface IMoveExpander +{ + /// + /// Probe the world and populate with feasible + /// neighbors. Returns the number of neighbors written. Implementations + /// must not write past the buffer; callers must size it to the expander's + /// max output. + /// + int Expand(CalculationContext ctx, int x, int y, int z, Span buffer); + + /// + /// Upper bound on the number of neighbors this expander can emit from a + /// single node. Used by the driver to size the per-node buffer. + /// + int MaxNeighbors { get; } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveClimb.cs b/MinecraftClient/Pathing/Moves/Impl/MoveClimb.cs new file mode 100644 index 0000000000..25312460b6 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveClimb.cs @@ -0,0 +1,76 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Climb up or down a ladder/vine at the current X,Z position. + /// + public sealed class MoveClimb : IMove + { + public MoveType Type => MoveType.Climb; + public int XOffset => 0; + public int ZOffset => 0; + public bool DynamicY => false; + + private readonly bool _up; + + public MoveClimb(bool up) + { + _up = up; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + var currentMat = ctx.GetMaterial(x, y, z); + if (!MoveHelper.IsClimbable(currentMat)) + { + result.SetImpossible(); + return; + } + + if (_up) + { + int destY = y + 1; + if (!ctx.CanWalkThrough(x, destY + 1, z)) + { + result.SetImpossible(); + return; + } + + var aboveMat = ctx.GetMaterial(x, destY, z); + if (MoveHelper.IsClimbable(aboveMat)) + { + result.Set(x, destY, z, ActionCosts.LadderUpOne); + return; + } + + // Top of climbable: only allow if we can transition to a solid + // surface nearby (ladders have wall collision, vines don't). + // Check if the destination block itself is walkable-through and + // there's solid ground at (x, destY-1, z) -- meaning we can + // stand at destY. This handles ladder-tops where the ladder ends + // but the block above is air and we can step onto the floor. + if (!aboveMat.IsSolid() && ctx.CanWalkOn(x, destY - 1, z)) + { + result.Set(x, destY, z, ActionCosts.LadderUpOne); + return; + } + + result.SetImpossible(); + } + else + { + int destY = y - 1; + var belowMat = ctx.GetMaterial(x, destY, z); + if (MoveHelper.IsClimbable(belowMat) || !belowMat.IsSolid()) + { + result.Set(x, destY, z, ActionCosts.LadderDownOne); + return; + } + + result.SetImpossible(); + } + } + } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveDescend.cs b/MinecraftClient/Pathing/Moves/Impl/MoveDescend.cs new file mode 100644 index 0000000000..f1141f1749 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveDescend.cs @@ -0,0 +1,167 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Walk off a ledge and drop 1-N blocks in a cardinal direction. + /// For short drops (1-MaxFallHeight), uses simple scan. + /// For longer drops, delegates to DynamicFallCost which supports: + /// - Water/liquid safe landing + /// - Mid-fall ladder/vine grabbing (resets effective fall height if ≤ 11 blocks) + /// Based on Baritone's MovementDescend.dynamicFallCost design. + /// + public sealed class MoveDescend : IMove + { + public MoveType Type => MoveType.Descend; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => true; + + public MoveDescend(int xOffset, int zOffset) + { + XOffset = xOffset; + ZOffset = zOffset; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + int destX = x + XOffset; + int destZ = z + ZOffset; + + if (!ctx.CanWalkThrough(destX, y, destZ) || !ctx.CanWalkThrough(destX, y + 1, destZ)) + { + result.SetImpossible(); + return; + } + + // The landing feet column must also be passable. Without this, a descend + // into a column whose y-1 block is solid (e.g. a 2-block thick platform top + // where (destX,y-1) is stone and (destX,y-2) is also stone) would be + // accepted: the solid y-2 floor satisfies CanWalkOn, the y/y+1 body space + // satisfies the step, but physically the bot just walks onto the solid + // y-1 block at the same feet level and the Descend template waits forever + // for a drop that can never happen -- producing an infinite replan loop. + if (!ctx.CanWalkThrough(destX, y - 1, destZ)) + { + result.SetImpossible(); + return; + } + + // Don't descend from ladder/vine (unreliable) + Material fromDown = ctx.GetMaterial(x, y - 1, z); + if (fromDown.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + // Check for simple 1-block descend first (most common case) + if (ctx.CanWalkOn(destX, y - 2, destZ)) + { + Material landOn = ctx.GetMaterial(destX, y - 2, destZ); + if (MoveHelper.IsHazardous(landOn)) + { + result.SetImpossible(); + return; + } + if (ctx.GetMaterial(destX, y - 1, destZ).CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + double cost = ActionCosts.WalkOffBlock + ActionCosts.FallCost(1); + result.Set(destX, y - 1, destZ, cost); + return; + } + + // Not a simple 1-block drop, try dynamic fall + DynamicFallCost(ctx, x, y, z, destX, destZ, ref result); + } + + /// + /// Scan downward for a safe landing, supporting water, ladder grabs, and + /// configurable max heights. Based on Baritone's dynamicFallCost. + /// + private static void DynamicFallCost( + CalculationContext ctx, int x, int y, int z, + int destX, int destZ, ref MoveResult result) + { + if (!ctx.CanWalkThrough(destX, y - 2, destZ)) + { + result.SetImpossible(); + return; + } + + double costSoFar = 0; + int effectiveStartHeight = y; + + // Scan starts from fallHeight=3 (2 blocks below the ledge) + // because fallHeight=1 and =2 were already checked above + int maxScan = ctx.MaxFallHeightWater > ctx.MaxFallHeight + ? ctx.MaxFallHeightWater + : ctx.MaxFallHeight; + + for (int fallHeight = 3; fallHeight <= maxScan; fallHeight++) + { + int newY = y - fallHeight; + if (newY < -64) break; + + Material ontoMat = ctx.GetMaterial(destX, newY, destZ); + + int unprotectedFallHeight = fallHeight - (y - effectiveStartHeight); + double tentativeCost = ActionCosts.WalkOffBlock + + ActionCosts.FallCost(unprotectedFallHeight) + costSoFar; + + // Water landing: safe regardless of height (water absorbs all fall damage) + if (MoveHelper.IsWater(ontoMat)) + { + result.Set(destX, newY, destZ, tentativeCost); + return; + } + + // Mid-fall ladder/vine grab: resets effective fall height. + // Vanilla: player grabs ladders/vines if falling speed is low enough + // (roughly ≤ 11 blocks of unprotected free fall). + if (ctx.AllowLadderGrabDuringFall && unprotectedFallHeight <= 11 + && ontoMat.CanBeClimbedOn()) + { + costSoFar += ActionCosts.FallCost(unprotectedFallHeight - 1); + costSoFar += ActionCosts.LadderDownOne; + effectiveStartHeight = newY; + continue; + } + + // Air or passable: continue falling + if (ctx.CanWalkThrough(destX, newY, destZ)) + continue; + + // Hit something solid + if (MoveHelper.IsHazardous(ontoMat)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkOn(destX, newY, destZ)) + { + result.SetImpossible(); + return; + } + + // Solid landing: allowed if within safe fall height + if (unprotectedFallHeight <= ctx.MaxFallHeight + 1) + { + result.Set(destX, newY + 1, destZ, tentativeCost); + return; + } + + result.SetImpossible(); + return; + } + + result.SetImpossible(); + } + } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveFall.cs b/MinecraftClient/Pathing/Moves/Impl/MoveFall.cs new file mode 100644 index 0000000000..da92f86ccf --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveFall.cs @@ -0,0 +1,94 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Straight-down fall at the current X,Z position. + /// Supports water landing and mid-fall ladder/vine grabbing. + /// Used for drops where MoveDescend's 1-block horizontal offset doesn't apply. + /// + public sealed class MoveFall : IMove + { + public MoveType Type => MoveType.Fall; + public int XOffset => 0; + public int ZOffset => 0; + public bool DynamicY => true; + + private readonly int _maxScanDepth; + + public MoveFall(int maxScanDepth = 256) + { + _maxScanDepth = maxScanDepth; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + if (!ctx.CanWalkThrough(x, y - 1, z)) + { + result.SetImpossible(); + return; + } + + double costSoFar = 0; + int effectiveStartHeight = y; + + for (int fallDist = 1; fallDist <= _maxScanDepth; fallDist++) + { + int landY = y - fallDist; + if (landY < -64) break; + + Material ontoMat = ctx.GetMaterial(x, landY, z); + int unprotectedFallHeight = fallDist - (y - effectiveStartHeight); + + // Water landing: safe regardless of height + if (MoveHelper.IsWater(ontoMat)) + { + double waterCost = ActionCosts.FallCost(unprotectedFallHeight) + costSoFar; + result.Set(x, landY, z, waterCost); + return; + } + + // Mid-fall ladder/vine grab (resets effective fall height) + if (ctx.AllowLadderGrabDuringFall && unprotectedFallHeight <= 11 + && ontoMat.CanBeClimbedOn()) + { + costSoFar += ActionCosts.FallCost(unprotectedFallHeight - 1); + costSoFar += ActionCosts.LadderDownOne; + effectiveStartHeight = landY; + continue; + } + + if (ctx.CanWalkThrough(x, landY, z)) + continue; + + // Hit something solid + if (!ctx.CanWalkOn(x, landY, z)) + { + result.SetImpossible(); + return; + } + + if (MoveHelper.IsHazardous(ontoMat)) + { + result.SetImpossible(); + return; + } + + // Solid landing within safe height + if (unprotectedFallHeight <= ctx.MaxFallHeight + 1) + { + double cost = ActionCosts.FallCost(unprotectedFallHeight) + costSoFar; + result.Set(x, landY + 1, z, cost); + return; + } + + // Too high for safe landing + result.SetImpossible(); + return; + } + + result.SetImpossible(); + } + } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveJump.cs b/MinecraftClient/Pathing/Moves/Impl/MoveJump.cs new file mode 100644 index 0000000000..3e8c26859d --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveJump.cs @@ -0,0 +1,79 @@ +using System; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Unified jump-family move. A single IMove implementation that dispatches + /// on to cover every combination previously + /// implemented by seven separate classes (Traverse, Diagonal, Ascend, + /// DiagonalAscend, DiagonalDescend, Parkour, SidewallParkour). + /// + /// All feasibility and cost logic lives in . + /// Factory helpers (, , ...) + /// produce the right descriptor for each use site without requiring + /// callers to remember the mapping between flavor and . + /// + public sealed class MoveJump : IMove + { + public JumpDescriptor Descriptor { get; } + public MoveType Type { get; } + public int XOffset => Descriptor.XOffset; + public int ZOffset => Descriptor.ZOffset; + public int YDelta => Descriptor.YDelta; + public JumpFlavor Flavor => Descriptor.Flavor; + public bool DynamicY => false; + + public MoveJump(JumpDescriptor descriptor) + { + Descriptor = descriptor; + Type = DeriveMoveType(descriptor); + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + => JumpFeasibility.Evaluate(ctx, x, y, z, Descriptor, ref result); + + public override string ToString() + { + double horiz = Math.Sqrt((double)(XOffset * XOffset + ZOffset * ZOffset)); + return $"MoveJump({Flavor}, off=({XOffset},{ZOffset}), dy={YDelta}, dist={horiz:F2})"; + } + + // ----------------------------------------------------------------- + // Factory helpers + // ----------------------------------------------------------------- + + public static MoveJump Traverse(int dx, int dz) + => new(new JumpDescriptor(dx, dz, 0, JumpFlavor.Walk)); + + public static MoveJump Diagonal(int dx, int dz) + => new(new JumpDescriptor(dx, dz, 0, JumpFlavor.Walk)); + + public static MoveJump Ascend(int dx, int dz) + => new(new JumpDescriptor(dx, dz, 1, JumpFlavor.Step)); + + public static MoveJump DiagonalAscend(int dx, int dz) + => new(new JumpDescriptor(dx, dz, 1, JumpFlavor.Step)); + + public static MoveJump DiagonalDescend(int dx, int dz) + => new(new JumpDescriptor(dx, dz, -1, JumpFlavor.Step)); + + public static MoveJump Parkour(int dx, int dz, int yDelta = 0) + => new(new JumpDescriptor(dx, dz, yDelta, JumpFlavor.SprintJump)); + + public static MoveJump Sidewall(int dx, int dz, int yDelta = 0) + => new(new JumpDescriptor(dx, dz, yDelta, JumpFlavor.Sidewall)); + + private static MoveType DeriveMoveType(JumpDescriptor d) + { + return d.Flavor switch + { + JumpFlavor.Walk => d.IsCardinal ? MoveType.Traverse : MoveType.Diagonal, + JumpFlavor.Step => d.YDelta > 0 ? MoveType.Ascend : MoveType.Descend, + JumpFlavor.SprintJump => MoveType.Parkour, + JumpFlavor.Sidewall => MoveType.Parkour, + _ => MoveType.Traverse, + }; + } + } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs b/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs new file mode 100644 index 0000000000..9a1c6d6675 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs @@ -0,0 +1,125 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Sprint off a ledge and land 2 blocks away horizontally while dropping 1-3 blocks. + /// At sprint speed (~5.6 blocks/s), falling 1-3 blocks gives enough airtime to + /// cover 2 horizontal blocks without needing a jump. + /// Supports cardinal (2,0)/(0,2) and diagonal (1,1) offsets. + /// + public sealed class MoveSprintDescend : IMove + { + public MoveType Type => MoveType.Descend; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => true; + + public MoveSprintDescend(int xOffset, int zOffset) + { + XOffset = xOffset; + ZOffset = zOffset; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + if (!ctx.CanSprint) + { + result.SetImpossible(); + return; + } + + int destX = x + XOffset; + int destZ = z + ZOffset; + + Material fromDown = ctx.GetMaterial(x, y - 1, z); + if (fromDown.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + int xSign = Math.Sign(XOffset); + int zSign = Math.Sign(ZOffset); + int xAbs = Math.Abs(XOffset); + int zAbs = Math.Abs(ZOffset); + + // Check body clearance at the destination column and along the flight path. + if (!ctx.CanWalkThrough(destX, y, destZ) || !ctx.CanWalkThrough(destX, y + 1, destZ)) + { + result.SetImpossible(); + return; + } + + // For cardinal (2,0)/(0,2): check the one intermediate column. + // For diagonal (1,1): destination IS one step away, no intermediate. + if (xAbs == 2 && zAbs == 0) + { + if (!ctx.CanWalkThrough(x + xSign, y, z) || !ctx.CanWalkThrough(x + xSign, y + 1, z)) + { result.SetImpossible(); return; } + } + else if (xAbs == 0 && zAbs == 2) + { + if (!ctx.CanWalkThrough(x, y, z + zSign) || !ctx.CanWalkThrough(x, y + 1, z + zSign)) + { result.SetImpossible(); return; } + } + + // The first step in the primary direction must lack ground (this IS a drop). + if (xAbs > 0 && zAbs == 0) + { + if (ctx.CanWalkOn(x + xSign, y - 1, z)) + { + result.SetImpossible(); + return; + } + } + else if (xAbs == 0 && zAbs > 0) + { + if (ctx.CanWalkOn(x, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + } + else + { + if (ctx.CanWalkOn(x + xSign, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + } + + // Scan downward from destination column for a landing spot. + double horizDist = Math.Sqrt((double)(XOffset * XOffset + ZOffset * ZOffset)); + for (int drop = 1; drop <= ctx.MaxFallHeight; drop++) + { + int landY = y - drop - 1; + if (landY < -64) break; + + if (!ctx.CanWalkOn(destX, landY, destZ)) + continue; + + Material landMat = ctx.GetMaterial(destX, landY, destZ); + if (MoveHelper.IsHazardous(landMat)) + { + result.SetImpossible(); + return; + } + + // Body space at landing + if (!ctx.CanWalkThrough(destX, landY + 1, destZ) || + !ctx.CanWalkThrough(destX, landY + 2, destZ)) + continue; + + double cost = horizDist * ctx.SprintCost + ActionCosts.FallCost(drop); + result.Set(destX, landY + 1, destZ, cost); + return; + } + + result.SetImpossible(); + } + } +} diff --git a/MinecraftClient/Pathing/Moves/JumpDescriptor.cs b/MinecraftClient/Pathing/Moves/JumpDescriptor.cs new file mode 100644 index 0000000000..e44843bdf6 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/JumpDescriptor.cs @@ -0,0 +1,56 @@ +namespace MinecraftClient.Pathing.Moves; + +/// +/// Kind of jump-family move. Each flavor selects a different evaluator path +/// inside while sharing low-level primitives +/// (head clearance, destination clearance, flight-path sweep, cost model). +/// +public enum JumpFlavor +{ + /// + /// Single-block cardinal or diagonal walk at the same Y. No jump input. + /// Covers the old MoveTraverse and MoveDiagonal. + /// + Walk, + + /// + /// Single-block vertical step (dy = +1 up or dy = -1 down), cardinal or + /// diagonal. Covers MoveAscend, MoveDiagonalAscend, and + /// MoveDiagonalDescend. + /// + Step, + + /// + /// Multi-block sprint jump, cardinal or diagonal. Covers MoveParkour. + /// + SprintJump, + + /// + /// Dominant-axis sprint jump that uses an inner wall for support. Covers + /// MoveSidewallParkour. + /// + Sidewall, +} + +/// +/// Fully describes a single jump-family move (Walk / Step / SprintJump / +/// Sidewall). All geometry that downstream planners or templates need can be +/// derived from this value, so A* only needs to enumerate descriptors rather +/// than hard-coded IMove subclasses. +/// +public readonly record struct JumpDescriptor( + int XOffset, + int ZOffset, + int YDelta, + JumpFlavor Flavor) +{ + public bool IsCardinal => (XOffset == 0) != (ZOffset == 0); + + public bool IsDiagonal => XOffset != 0 && ZOffset != 0; + + public int HorizontalMajor + => System.Math.Max(System.Math.Abs(XOffset), System.Math.Abs(ZOffset)); + + public int HorizontalMinor + => System.Math.Min(System.Math.Abs(XOffset), System.Math.Abs(ZOffset)); +} diff --git a/MinecraftClient/Pathing/Moves/JumpExpander.cs b/MinecraftClient/Pathing/Moves/JumpExpander.cs new file mode 100644 index 0000000000..b07abdb7e9 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/JumpExpander.cs @@ -0,0 +1,480 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves.Impl; + +namespace MinecraftClient.Pathing.Moves; + +/// +/// Dynamic expander for every move in the jump family (Walk, Step, +/// SprintJump, Sidewall). Walk, Step, diagonal SprintJump and Sidewall are +/// still driven by a declarative descriptor table that calls +/// one entry at a time. Cardinal +/// SprintJumps are produced by , a Baritone-style +/// near-to-far scan that emits at most one candidate per direction -- letting +/// A* re-probe from each landing instead of enumerating every (distance, +/// yDelta) combination. +/// +/// The hot path hoists per-node guards (AllowParkour, head clearance, +/// takeoff material, adjacent-wall presence) and precomputes an 8-direction +/// "first-step has no floor" table so entire descriptor groups can be +/// rejected in O(1) before touching . Ordinary +/// ground-walking nodes skip every jump descriptor this way; nodes without +/// any adjacent wall skip all sidewall descriptors. +/// +public sealed class JumpExpander : IMoveExpander +{ + /// + /// Extra slots in the neighbor buffer for the 4 cardinal probes. Each + /// probe may emit up to one sprint-jump candidate per yDelta (4 total) + /// plus up to one sidewall candidate per (lateral sign, yDelta) (8 + /// total), so 4 directions * (4 + 8) = 48 slots. The probe almost never + /// fills every slot; this is a generous upper bound that keeps the + /// neighbor buffer stack-allocated. + /// + private const int CardinalProbeSlots = 48; + + private static readonly JumpDescriptor[] _descriptors = BuildDescriptors(); + + public int MaxNeighbors => _descriptors.Length + CardinalProbeSlots; + + public int Expand(CalculationContext ctx, int x, int y, int z, Span buffer) + { + int count = 0; + MoveResult result = default; + + // ---- Per-node preconditions shared by every jump-family move ---- + // These are the first checks JumpFeasibility.Evaluate* would make. + // Hoisting them once turns many method calls per node into one + // branch in the hot path. "canSprintTakeoff" gates both the + // descriptor loop's SprintJump entries and all four cardinal probes. + bool jumpFamilyAllowed = ctx.AllowParkour && ctx.CanSprint; + bool canSprintTakeoff = false; + if (jumpFamilyAllowed) + { + Material standingOn = ctx.GetMaterial(x, y - 1, z); + Material atFeet = ctx.GetMaterial(x, y, z); + canSprintTakeoff = + !standingOn.CanBeClimbedOn() + && !atFeet.IsLiquid() + && ctx.CanWalkThrough(x, y + 2, z); + } + + // ---- Per-direction gap table (SprintJump only) ---- + // Gap check: "first block adjacent to start must lack ground" so A* can't + // pick a cheaper walking path. For an octant (sx, sz) the cell is at + // (x+sx, y-1, z+sz). Index = (sx+1)*3 + (sz+1) over sx,sz in {-1,0,1}. + // If the floor is present for a direction, every SprintJump descriptor in + // that octant is infeasible. 9 slots (center slot 4 unused) fit cleanly + // on the stack. + Span directionGapOpen = stackalloc bool[9]; + if (canSprintTakeoff) + { + for (int dx = -1; dx <= 1; dx++) + { + for (int dz = -1; dz <= 1; dz++) + { + if (dx == 0 && dz == 0) + continue; + int idx = ((dx + 1) * 3) + (dz + 1); + directionGapOpen[idx] = !ctx.CanWalkOn(x + dx, y - 1, z + dz); + } + } + } + + for (int i = 0; i < _descriptors.Length; i++) + { + JumpDescriptor desc = _descriptors[i]; + + switch (desc.Flavor) + { + case JumpFlavor.SprintJump: + if (!canSprintTakeoff) + continue; + { + int sx = Math.Sign(desc.XOffset); + int sz = Math.Sign(desc.ZOffset); + int idx = ((sx + 1) * 3) + (sz + 1); + if (!directionGapOpen[idx]) + continue; + } + break; + case JumpFlavor.Sidewall: + // Sidewall candidates are produced by ProbeCardinal now. + continue; + default: + break; + } + + result.Cost = 0; + JumpFeasibility.Evaluate(ctx, x, y, z, desc, ref result); + if (result.IsImpossible) + continue; + + MoveType type = DeriveMoveType(desc); + if (count < buffer.Length) + buffer[count++] = new MoveNeighbor(result, type); + } + + // ---- Cardinal SprintJump probes (Baritone-style near-to-far scan) ---- + // Each cardinal direction probes distance 2..5 and emits at most one + // candidate (the closest feasible landing). A* re-probes from that + // landing to discover longer variants, which keeps the frontier small + // while preserving reachability. + if (canSprintTakeoff) + { + ProbeCardinal(ctx, x, y, z, +1, 0, directionGapOpen, buffer, ref count, ref result); + ProbeCardinal(ctx, x, y, z, -1, 0, directionGapOpen, buffer, ref count, ref result); + ProbeCardinal(ctx, x, y, z, 0, +1, directionGapOpen, buffer, ref count, ref result); + ProbeCardinal(ctx, x, y, z, 0, -1, directionGapOpen, buffer, ref count, ref result); + } + + return count; + } + + /// + /// Scans a single cardinal direction (fx, fz) for both sprint-jump + /// and sidewall landings. The forward air corridor is swept once + /// (Baritone-style monotonic scan with early break on obstruction) and + /// every feasible landing shape shares that sweep. Per-(lateral, yDelta) + /// sidewall candidates use the same i iteration to locate their + /// landing on the lateral column, so a single O(5) scan replaces the + /// ~8 + 16 static descriptor entries this direction used to need. + /// + /// Instead of emitting the closest valid landing (Baritone's choice), + /// the probe records the farthest valid landing per shape bucket and + /// emits one candidate each. Preferring the longer jump keeps A*'s path + /// cost low and avoids chains of short d=2 parkour jumps that MCC's + /// template can overshoot when sprint momentum is carried over. + /// + private static void ProbeCardinal( + CalculationContext ctx, + int x, int y, int z, + int fx, int fz, + ReadOnlySpan directionGapOpen, + Span buffer, + ref int count, + ref MoveResult result) + { + // If the first step has a floor, a cheaper Walk move covers this + // direction already (Baritone: "don't parkour if we could just + // traverse"). Use the precomputed gap table. + int firstStepIdx = ((fx + 1) * 3) + (fz + 1); + if (!directionGapOpen[firstStepIdx]) + return; + + // The first step's column (y, y+1) must be passable; without it the + // player hits a wall before leaving the takeoff block. (y+2 over the + // takeoff itself is guaranteed by canSprintTakeoff.) + int sx1 = x + fx; + int sz1 = z + fz; + if (!ctx.CanWalkThrough(sx1, y, sz1) || !ctx.CanWalkThrough(sx1, y + 1, sz1)) + return; + + // Lateral unit vectors perpendicular to (fx, fz). Positive and + // negative sides are tracked independently so the wall presence + // short-circuit applies per side. + int lxP, lzP, lxN, lzN; + if (fx != 0) + { + lxP = 0; lzP = +1; + lxN = 0; lzN = -1; + } + else + { + lxP = +1; lzP = 0; + lxN = -1; lzN = 0; + } + + // Sidewall needs a solid block immediately lateral to the takeoff + // (step=0 in HasSidewallArcClearance). If that cell is walk-through + // at both y and y+1, no sidewall candidate from this takeoff can + // succeed along that lateral sign. + bool wallP = !ctx.CanWalkThrough(x + lxP, y, z + lzP) + || !ctx.CanWalkThrough(x + lxP, y + 1, z + lzP); + bool wallN = !ctx.CanWalkThrough(x + lxN, y, z + lzN) + || !ctx.CanWalkThrough(x + lxN, y + 1, z + lzN); + + // Farthest valid i for each sprint-jump shape. + int bestAscend = 0; + int bestFlat = 0; + int bestDescend1 = 0; + int bestDescend2 = 0; + + // Farthest valid i per (lateral sign, yDelta) for sidewall. + // yDelta indices: 0=+1, 1=0, 2=-1, 3=-2. + int bestSwP0 = 0, bestSwP1 = 0, bestSwP2 = 0, bestSwP3 = 0; + int bestSwN0 = 0, bestSwN1 = 0, bestSwN2 = 0, bestSwN3 = 0; + + const int MaxJumpDistance = 5; + for (int i = 2; i <= MaxJumpDistance; i++) + { + int dx = x + fx * i; + int dz = z + fz * i; + + // Shared head-height air corridor. If blocked the whole arc is + // interrupted; every larger i is also unreachable for both + // sprint jump and sidewall. + if (!ctx.CanWalkThrough(dx, y + 1, dz) || !ctx.CanWalkThrough(dx, y + 2, dz)) + break; + + if (!ctx.CanWalkThrough(dx, y, dz)) + { + // Foot-height is blocked. Only sprint-jump ascend is + // potentially viable here, and only for i <= 3. Sidewall's + // HasSidewallArcClearance requires a clear forward column + // at every step, so no sidewall candidate survives past + // this obstruction either. + if (i <= 3 && ctx.CanWalkOn(dx, y, dz)) + bestAscend = i; + break; + } + + // Foot-height is clear; record the best forward-axis landing. + if (ctx.CanWalkOn(dx, y - 1, dz)) + bestFlat = i; + else if (ctx.CanWalkOn(dx, y - 2, dz)) + bestDescend1 = i; + else if (ctx.CanWalkOn(dx, y - 3, dz)) + bestDescend2 = i; + + // Sidewall candidates land on the lateral column. The forward + // corridor has already been validated above; HasSidewallArc- + // Clearance's wall-depth and outside-lateral checks are deferred + // to EvaluateSidewall. + if (wallP) + TrackSidewallCandidates(ctx, dx, y, dz, lxP, lzP, i, + ref bestSwP0, ref bestSwP1, ref bestSwP2, ref bestSwP3); + if (wallN) + TrackSidewallCandidates(ctx, dx, y, dz, lxN, lzN, i, + ref bestSwN0, ref bestSwN1, ref bestSwN2, ref bestSwN3); + } + + // Emit sprint-jump bests (MoveType.Parkour). + if (bestAscend > 0) + TryEmitSprintJump(ctx, x, y, z, fx * bestAscend, fz * bestAscend, +1, buffer, ref count, ref result); + if (bestFlat > 0) + TryEmitSprintJump(ctx, x, y, z, fx * bestFlat, fz * bestFlat, 0, buffer, ref count, ref result); + if (bestDescend1 > 0) + TryEmitSprintJump(ctx, x, y, z, fx * bestDescend1, fz * bestDescend1, -1, buffer, ref count, ref result); + if (bestDescend2 > 0) + TryEmitSprintJump(ctx, x, y, z, fx * bestDescend2, fz * bestDescend2, -2, buffer, ref count, ref result); + + // Emit sidewall bests, one candidate per (lateral sign, yDelta). + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxP, lzP, +1, bestSwP0, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxP, lzP, 0, bestSwP1, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxP, lzP, -1, bestSwP2, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxP, lzP, -2, bestSwP3, buffer, ref count, ref result); + + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxN, lzN, +1, bestSwN0, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxN, lzN, 0, bestSwN1, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxN, lzN, -1, bestSwN2, buffer, ref count, ref result); + EmitSidewallIfAny(ctx, x, y, z, fx, fz, lxN, lzN, -2, bestSwN3, buffer, ref count, ref result); + } + + /// + /// Cheap per-i pre-check for sidewall candidates. Updates the + /// per-yDelta "farthest valid i" buckets whenever the lateral landing + /// column matches the y offset. The expensive full feasibility check + /// ( etc.) is + /// still performed by + /// on emission; this pre-check just filters out trivially-impossible + /// iterations so Evaluate runs at most 8 times per direction. + /// + private static void TrackSidewallCandidates( + CalculationContext ctx, + int dx, int y, int dz, + int lateralX, int lateralZ, + int i, + ref int bestPlus1, + ref int bestFlat, + ref int bestMinus1, + ref int bestMinus2) + { + int lx = dx + lateralX; + int lz = dz + lateralZ; + + // yDelta = +1 (ascend). Only meaningful for i <= 3. + if (i <= 3 + && ctx.CanWalkOn(lx, y, lz) + && ctx.CanWalkThrough(lx, y + 1, lz) + && ctx.CanWalkThrough(lx, y + 2, lz)) + { + bestPlus1 = i; + } + + // Destination column body clearance at flat/descend heights. + if (!ctx.CanWalkThrough(lx, y, lz) || !ctx.CanWalkThrough(lx, y + 1, lz)) + return; + + if (ctx.CanWalkOn(lx, y - 1, lz)) + bestFlat = i; + else if (ctx.CanWalkOn(lx, y - 2, lz)) + bestMinus1 = i; + else if (ctx.CanWalkOn(lx, y - 3, lz)) + bestMinus2 = i; + } + + private static void EmitSidewallIfAny( + CalculationContext ctx, + int x, int y, int z, + int fx, int fz, + int lateralX, int lateralZ, + int yDelta, + int bestI, + Span buffer, + ref int count, + ref MoveResult result) + { + if (bestI <= 0) + return; + + int xOffset = fx * bestI + lateralX; + int zOffset = fz * bestI + lateralZ; + JumpDescriptor desc = new(xOffset, zOffset, yDelta, JumpFlavor.Sidewall); + result.Cost = 0; + JumpFeasibility.Evaluate(ctx, x, y, z, desc, ref result); + if (result.IsImpossible) + return; + + if (count < buffer.Length) + buffer[count++] = new MoveNeighbor(result, MoveType.Parkour); + } + + /// + /// Builds a cardinal descriptor for + /// the probed shape and delegates to . + /// The descriptor table and this probe share a single source of truth for + /// run-up, flight path, overshoot, cost, and entry preparation. + /// + private static void TryEmitSprintJump( + CalculationContext ctx, + int x, int y, int z, + int xOffset, int zOffset, int yDelta, + Span buffer, + ref int count, + ref MoveResult result) + { + JumpDescriptor desc = new(xOffset, zOffset, yDelta, JumpFlavor.SprintJump); + result.Cost = 0; + JumpFeasibility.Evaluate(ctx, x, y, z, desc, ref result); + if (result.IsImpossible) + return; + + if (count < buffer.Length) + buffer[count++] = new MoveNeighbor(result, MoveType.Parkour); + } + + private static MoveType DeriveMoveType(JumpDescriptor d) => d.Flavor switch + { + JumpFlavor.Walk => d.IsCardinal ? MoveType.Traverse : MoveType.Diagonal, + JumpFlavor.Step => d.YDelta > 0 ? MoveType.Ascend : MoveType.Descend, + JumpFlavor.SprintJump => MoveType.Parkour, + JumpFlavor.Sidewall => MoveType.Parkour, + _ => MoveType.Traverse, + }; + + private static JumpDescriptor[] BuildDescriptors() + { + var list = new System.Collections.Generic.List(256); + int[] offsets = [1, -1]; + + // Cardinal walk + 1-block ascend + foreach (int dx in offsets) + { + list.Add(new JumpDescriptor(dx, 0, 0, JumpFlavor.Walk)); + list.Add(new JumpDescriptor(dx, 0, 1, JumpFlavor.Step)); + } + foreach (int dz in offsets) + { + list.Add(new JumpDescriptor(0, dz, 0, JumpFlavor.Walk)); + list.Add(new JumpDescriptor(0, dz, 1, JumpFlavor.Step)); + } + + // Diagonal walk + diagonal ascend/descend + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + list.Add(new JumpDescriptor(dx, dz, 0, JumpFlavor.Walk)); + list.Add(new JumpDescriptor(dx, dz, 1, JumpFlavor.Step)); + list.Add(new JumpDescriptor(dx, dz, -1, JumpFlavor.Step)); + } + } + + // Cardinal parkour is handled dynamically by ProbeCardinal; only the + // diagonal SprintJump variants remain as static descriptors. + + // Diagonal parkour + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + list.Add(new JumpDescriptor(dx * 2, dz * 1, 0, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 1, dz * 2, 0, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 2, dz * 2, 0, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 3, dz * 1, 0, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 1, dz * 3, 0, JumpFlavor.SprintJump)); + + list.Add(new JumpDescriptor(dx * 2, dz * 1, -1, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 1, dz * 2, -1, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 2, dz * 2, -1, JumpFlavor.SprintJump)); + + list.Add(new JumpDescriptor(dx * 2, dz * 1, 1, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 1, dz * 2, 1, JumpFlavor.SprintJump)); + list.Add(new JumpDescriptor(dx * 2, dz * 2, 1, JumpFlavor.SprintJump)); + } + } + + // Sidewall parkour is produced by ProbeCardinal alongside cardinal + // sprint jumps -- the probe shares a single forward-corridor scan + // with the sprint-jump candidates and emits a sidewall candidate + // whenever a lateral wall supports it. + + return list.ToArray(); + } + + /// + /// Read-only snapshot of the descriptor table used by this expander. + /// Contains only moves that are enumerated statically (Walk, Step, + /// diagonal SprintJump); cardinal SprintJump and Sidewall are produced + /// dynamically by . + /// + public static ReadOnlySpan Descriptors => _descriptors; +} + +/// +/// Thin adapter that wraps an array of legacy instances as +/// an . Used for the dynamic-landing and vertical +/// move families (MoveDescend, MoveSprintDescend, +/// MoveClimb, MoveFall) which do not fit the JumpDescriptor model. +/// +public sealed class LegacyMoveExpander : IMoveExpander +{ + private readonly IMove[] _moves; + + public LegacyMoveExpander(IMove[] moves) + { + _moves = moves ?? throw new ArgumentNullException(nameof(moves)); + } + + public int MaxNeighbors => _moves.Length; + + public int Expand(CalculationContext ctx, int x, int y, int z, Span buffer) + { + int count = 0; + MoveResult result = default; + for (int i = 0; i < _moves.Length; i++) + { + IMove move = _moves[i]; + result.Cost = 0; + move.Calculate(ctx, x, y, z, ref result); + if (result.IsImpossible) + continue; + + if (count < buffer.Length) + buffer[count++] = new MoveNeighbor(result, move.Type); + } + return count; + } +} diff --git a/MinecraftClient/Pathing/Moves/JumpFeasibility.cs b/MinecraftClient/Pathing/Moves/JumpFeasibility.cs new file mode 100644 index 0000000000..8054402f0c --- /dev/null +++ b/MinecraftClient/Pathing/Moves/JumpFeasibility.cs @@ -0,0 +1,606 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Moves; + +/// +/// Single source of truth for jump-family feasibility and cost. Each +/// selects one of the Evaluate* methods; the methods +/// share low-level primitives (head clearance, destination clearance, +/// flight-path sweep, run-up, cost) so that a physics rule is implemented +/// exactly once. +/// +internal static class JumpFeasibility +{ + public static void Evaluate( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + switch (desc.Flavor) + { + case JumpFlavor.Walk: + EvaluateWalk(ctx, x, y, z, desc, ref result); + return; + case JumpFlavor.Step: + EvaluateStep(ctx, x, y, z, desc, ref result); + return; + case JumpFlavor.SprintJump: + EvaluateSprintJump(ctx, x, y, z, desc, ref result); + return; + case JumpFlavor.Sidewall: + EvaluateSidewall(ctx, x, y, z, desc, ref result); + return; + default: + result.SetImpossible(); + return; + } + } + + // --------------------------------------------------------------------- + // Walk (dy = 0, single block, cardinal or diagonal) + // --------------------------------------------------------------------- + + private static void EvaluateWalk( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + int dx = desc.XOffset; + int dz = desc.ZOffset; + int destX = x + dx; + int destZ = z + dz; + + if (!ctx.CanWalkThrough(destX, y, destZ) || !ctx.CanWalkThrough(destX, y + 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkOn(destX, y - 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (desc.IsCardinal) + { + double cost = ctx.SprintCost; + Material destFloor = ctx.GetMaterial(destX, y - 1, destZ); + if (destFloor == Material.SoulSand) + cost *= 1.0 / PhysicsConsts.SoulSandSpeedFactor; + result.Set(destX, y, destZ, cost); + return; + } + + // Diagonal corner walk: need at least one passable side cardinal. + bool sideX = ctx.CanWalkThrough(x + dx, y, z) && + ctx.CanWalkThrough(x + dx, y + 1, z); + bool sideZ = ctx.CanWalkThrough(x, y, z + dz) && + ctx.CanWalkThrough(x, y + 1, z + dz); + + if (!sideX && !sideZ) + { + result.SetImpossible(); + return; + } + + double diagCost = ctx.SprintCost * ActionCosts.DiagonalMultiplier; + if (!sideX || !sideZ) + diagCost = ctx.WalkCost * ActionCosts.DiagonalMultiplier; + + result.Set(destX, y, destZ, diagCost); + } + + // --------------------------------------------------------------------- + // Step (dy = +1 ascend, dy = -1 descend, cardinal or diagonal) + // --------------------------------------------------------------------- + + private static void EvaluateStep( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + if (desc.YDelta == 1) + EvaluateStepAscend(ctx, x, y, z, desc, ref result); + else if (desc.YDelta == -1) + EvaluateStepDescend(ctx, x, y, z, desc, ref result); + else + result.SetImpossible(); + } + + private static void EvaluateStepAscend( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + int dx = desc.XOffset; + int dz = desc.ZOffset; + int destX = x + dx; + int destZ = z + dz; + int destY = y + 1; + + if (!ctx.CanWalkThrough(x, y + 2, z)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkOn(destX, y, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkThrough(destX, destY, destZ) || + !ctx.CanWalkThrough(destX, destY + 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (desc.IsCardinal) + { + double cost = ctx.SprintCost + ctx.JumpPenalty; + result.Set(destX, destY, destZ, cost); + return; + } + + bool pathViaX = ctx.CanWalkThrough(x + dx, y, z) && + ctx.CanWalkThrough(x + dx, y + 1, z) && + ctx.CanWalkThrough(x + dx, y + 2, z); + bool pathViaZ = ctx.CanWalkThrough(x, y, z + dz) && + ctx.CanWalkThrough(x, y + 1, z + dz) && + ctx.CanWalkThrough(x, y + 2, z + dz); + + if (!pathViaX && !pathViaZ) + { + result.SetImpossible(); + return; + } + + // Baritone-parity gate (MovementDiagonal.cost @197-200): when either + // cardinal shoulder also has solid ground below (i.e. the bot could + // walk that way first and then do a plain cardinal Ascend), refuse + // the diagonal Ascend. Executing a diagonal Ascend requires the + // bot's ground-speed momentum to already point along the diagonal at + // the moment of takeoff; when the preceding segment is a cardinal + // Walk the momentum is axis-aligned and the 2-tick yaw/input rotation + // during the handoff cannot redirect enough horizontal motion, so the + // bot consistently overshoots the target block. Forcing A* to spend + // the extra ~0.4 cost of a cardinal Walk + cardinal Ascend pair + // eliminates that execution failure while still leaving true "only + // reachable diagonally" setups (no cardinal floor support) on the + // table for scenarios that explicitly test the diagonal Step graph. + bool cardinalWalkableViaX = pathViaX && ctx.CanWalkOn(x + dx, y - 1, z); + bool cardinalWalkableViaZ = pathViaZ && ctx.CanWalkOn(x, y - 1, z + dz); + if (cardinalWalkableViaX || cardinalWalkableViaZ) + { + result.SetImpossible(); + return; + } + + double diagCost = ctx.SprintCost * ActionCosts.DiagonalMultiplier + ctx.JumpPenalty; + result.Set(destX, destY, destZ, diagCost); + } + + private static void EvaluateStepDescend( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + int dx = desc.XOffset; + int dz = desc.ZOffset; + int destX = x + dx; + int destZ = z + dz; + int destY = y - 1; + + if (!ctx.CanWalkOn(destX, destY - 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkThrough(destX, destY, destZ) || + !ctx.CanWalkThrough(destX, destY + 1, destZ)) + { + result.SetImpossible(); + return; + } + + Material landOn = ctx.GetMaterial(destX, destY - 1, destZ); + if (MoveHelper.IsHazardous(landOn)) + { + result.SetImpossible(); + return; + } + + Material fromDown = ctx.GetMaterial(x, y - 1, z); + if (fromDown.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + if (!desc.IsDiagonal) + { + // Currently only diagonal descend steps exist; cardinal descend is + // served by MoveDescend which supports dynamic fall depth. + result.SetImpossible(); + return; + } + + bool pathViaX = ctx.CanWalkThrough(x + dx, y, z) && + ctx.CanWalkThrough(x + dx, y + 1, z); + bool pathViaZ = ctx.CanWalkThrough(x, y, z + dz) && + ctx.CanWalkThrough(x, y + 1, z + dz); + + // A diagonal Step descend forces the player off the corner of the + // current standing block. Vanilla axis-separated collision resolves + // -X and -Z movement independently: when ONE cardinal shoulder is + // blocked by a wall, the matching velocity component is zeroed and + // the bot slides along the open axis only. If the open-axis column + // (e.g. (x, y-1, z+dz) when only pathViaZ is clear) lacks a floor, + // the bot falls straight down past the intended landing block at + // (x+dx, y-2, z+dz) into whatever solid surface lies further below + // — exactly the multi-block fall-through observed on the 251→244 + // route around (249,136,207). Require BOTH shoulder columns to be + // passable so the bot can actually clear the corner diagonally. + if (!pathViaX || !pathViaZ) + { + result.SetImpossible(); + return; + } + + double cost = ActionCosts.WalkOffBlock * ActionCosts.DiagonalMultiplier + + ActionCosts.FallCost(1); + result.Set(destX, destY, destZ, cost); + } + + // --------------------------------------------------------------------- + // SprintJump (parkour, horiz >= 2, dy in -2..+1) + // Ported 1:1 from MoveParkour.Calculate. + // --------------------------------------------------------------------- + + private static void EvaluateSprintJump( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + int xOffset = desc.XOffset; + int zOffset = desc.ZOffset; + int yDelta = desc.YDelta; + + if (!ctx.AllowParkour) + { + result.SetImpossible(); + return; + } + + if (yDelta > 0 && !ctx.AllowParkourAscend) + { + result.SetImpossible(); + return; + } + + if (yDelta < 0 && -yDelta > ctx.MaxFallHeight) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanSprint) + { + result.SetImpossible(); + return; + } + + bool cardinal = (xOffset == 0) != (zOffset == 0); + if (cardinal) + { + int distance = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + int maxDistance = yDelta switch + { + > 0 => 3, + < 0 => 5, + _ => 5, + }; + + if (distance > maxDistance) + { + result.SetImpossible(); + return; + } + } + + Material standingOn = ctx.GetMaterial(x, y - 1, z); + if (standingOn.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasRunUp(ctx, x, y, z, xOffset, zOffset, yDelta)) + { + result.SetImpossible(); + return; + } + + int destX = x + xOffset; + int destZ = z + zOffset; + int destY = y + yDelta; + + if (!ctx.CanWalkThrough(x, y + 2, z)) + { + result.SetImpossible(); + return; + } + + Material atFeet = ctx.GetMaterial(x, y, z); + if (atFeet.IsLiquid()) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkOn(destX, destY - 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkThrough(destX, destY, destZ) || + !ctx.CanWalkThrough(destX, destY + 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (ParkourFeasibility.HasIntermediateLandingConflict(ctx, x, y, z, xOffset, zOffset, yDelta)) + { + result.SetImpossible(); + return; + } + + int xSign = Math.Sign(xOffset); + int zSign = Math.Sign(zOffset); + int xAbs = Math.Abs(xOffset); + int zAbs = Math.Abs(zOffset); + + if (!CheckSprintJumpFlightPath(ctx, x, y, z, xSign, zSign, xAbs, zAbs, yDelta)) + { + result.SetImpossible(); + return; + } + + // Gap check: first block(s) adjacent to start must lack ground so A* + // cannot take a cheaper walking path. + if (xAbs > 0 && zAbs == 0) + { + if (ctx.CanWalkOn(x + xSign, y - 1, z)) + { + result.SetImpossible(); + return; + } + } + else if (xAbs == 0 && zAbs > 0) + { + if (ctx.CanWalkOn(x, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + } + else if (ctx.CanWalkOn(x + xSign, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasDiagonalShoulderClearance(ctx, x, y, z, xOffset, zOffset)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasCardinalSideClearance(ctx, x, y, z, xOffset, zOffset)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasLandingOvershootClearance(ctx, destX, destY, destZ, xSign, zSign)) + { + result.SetImpossible(); + return; + } + + double horizDist = Math.Sqrt((double)((xOffset * xOffset) + (zOffset * zOffset))); + double cost; + if (yDelta > 0) + cost = horizDist * ctx.SprintCost + ctx.JumpPenalty * 2; + else if (yDelta < 0) + cost = horizDist * ctx.SprintCost + ctx.JumpPenalty + + ActionCosts.FallCost(-yDelta); + else if (horizDist >= 3.5) + cost = horizDist * ctx.SprintCost + ctx.JumpPenalty; + else + cost = horizDist * ctx.WalkCost + ctx.JumpPenalty; + + result.Set(destX, destY, destZ, cost, ParkourProfile.Default); + } + + private static bool CheckSprintJumpFlightPath( + CalculationContext ctx, + int x, int y, int z, + int xSign, int zSign, int xAbs, int zAbs, + int yDelta) + { + if (xAbs == 0 || zAbs == 0) + { + for (int step = 1; step < Math.Max(xAbs, zAbs); step++) + { + int gx = x + xSign * (xAbs > 0 ? step : 0); + int gz = z + zSign * (zAbs > 0 ? step : 0); + if (!ClearSprintJumpColumn(ctx, gx, y, gz, yDelta)) + return false; + } + return true; + } + + int maxSteps = Math.Max(xAbs, zAbs); + for (int step = 1; step < maxSteps; step++) + { + double fx = (double)step * xAbs / maxSteps; + double fz = (double)step * zAbs / maxSteps; + + int ix = (int)Math.Round(fx); + int iz = (int)Math.Round(fz); + + int gx = x + xSign * ix; + int gz = z + zSign * iz; + + if (!ClearSprintJumpColumn(ctx, gx, y, gz, yDelta)) + return false; + + if (xAbs != zAbs) + { + double fracX = fx - Math.Floor(fx); + double fracZ = fz - Math.Floor(fz); + if (fracX > 0.2 && fracX < 0.8 && ix > 0 && ix < xAbs) + { + if (!ClearSprintJumpColumn(ctx, x + xSign * (ix - 1), y, gz, yDelta)) + return false; + } + if (fracZ > 0.2 && fracZ < 0.8 && iz > 0 && iz < zAbs) + { + if (!ClearSprintJumpColumn(ctx, gx, y, z + zSign * (iz - 1), yDelta)) + return false; + } + } + } + + return true; + } + + private static bool ClearSprintJumpColumn(CalculationContext ctx, int gx, int y, int gz, int yDelta) + { + if (!ctx.CanWalkThrough(gx, y, gz) || + !ctx.CanWalkThrough(gx, y + 1, gz) || + !ctx.CanWalkThrough(gx, y + 2, gz)) + return false; + if (yDelta > 0 && !ctx.CanWalkThrough(gx, y + 3, gz)) + return false; + return true; + } + + // --------------------------------------------------------------------- + // Sidewall (dominant-axis sprint jump with an inner-wall constraint). + // Ported 1:1 from MoveSidewallParkour.Calculate. + // --------------------------------------------------------------------- + + private static void EvaluateSidewall( + CalculationContext ctx, + int x, int y, int z, + JumpDescriptor desc, + ref MoveResult result) + { + int xOffset = desc.XOffset; + int zOffset = desc.ZOffset; + int yDelta = desc.YDelta; + + if (!ctx.AllowParkour || !ctx.CanSprint) + { + result.SetImpossible(); + return; + } + + if (yDelta > 0 && !ctx.AllowParkourAscend) + { + result.SetImpossible(); + return; + } + + if (yDelta < 0 && -yDelta > ctx.MaxFallHeight) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.IsSidewallProfile(xOffset, zOffset, yDelta)) + { + result.SetImpossible(); + return; + } + + Material standingOn = ctx.GetMaterial(x, y - 1, z); + if (standingOn.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + Material atFeet = ctx.GetMaterial(x, y, z); + if (atFeet.IsLiquid()) + { + result.SetImpossible(); + return; + } + + ParkourFeasibility.GetSidewallAxes(xOffset, zOffset, out int forwardX, out int forwardZ, out int lateralX, out int lateralZ); + + int destX = x + xOffset; + int destY = y + yDelta; + int destZ = z + zOffset; + + if (!ctx.CanWalkThrough(x, y + 2, z)) + { + result.SetImpossible(); + return; + } + + if (ParkourFeasibility.TryGetRequiredStaticEntryRunupSteps(ctx.PreviousMoveType, xOffset, zOffset, yDelta, out int requiredSteps)) + { + if (!ParkourFeasibility.HasPreparedRunup(ctx.CurrentEntryPreparation, x, y, z, forwardX, forwardZ, requiredSteps)) + { + result.SetImpossible(); + return; + } + } + else if (!ParkourFeasibility.HasDominantAxisRunUp(ctx, x, y, z, forwardX, forwardZ, xOffset, zOffset, yDelta)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasSidewallArcClearance(ctx, x, y, z, forwardX, forwardZ, lateralX, lateralZ, xOffset, zOffset, yDelta)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasSidewallLandingClearance(ctx, destX, destY, destZ, forwardX, forwardZ, lateralX, lateralZ)) + { + result.SetImpossible(); + return; + } + + double horizDist = Math.Sqrt((double)((xOffset * xOffset) + (zOffset * zOffset))); + double cost = yDelta switch + { + > 0 => horizDist * ctx.SprintCost + ctx.JumpPenalty * 2, + < 0 => horizDist * ctx.SprintCost + ctx.JumpPenalty + ActionCosts.FallCost(-yDelta), + _ => horizDist * ctx.SprintCost + ctx.JumpPenalty, + }; + + result.Set(destX, destY, destZ, cost, ParkourProfile.Sidewall); + } +} diff --git a/MinecraftClient/Pathing/Moves/MoveHelper.cs b/MinecraftClient/Pathing/Moves/MoveHelper.cs new file mode 100644 index 0000000000..63fc2c409d --- /dev/null +++ b/MinecraftClient/Pathing/Moves/MoveHelper.cs @@ -0,0 +1,114 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves +{ + /// + /// Block passability checks for path planning. + /// Uses Material-level checks initially; designed to allow future BlockShapes upgrade. + /// + public static class MoveHelper + { + /// + /// Can a player's body/head occupy this block position? (air, open door, tall grass, etc.) + /// + public static bool CanWalkThrough(CalculationContext ctx, int x, int y, int z) + { + Material mat = ctx.GetMaterial(x, y, z); + if (mat == Material.Air || mat == Material.CaveAir || mat == Material.VoidAir) + return true; + if (mat.IsLiquid()) + return false; + if (mat.CanBeClimbedOn()) + return true; + if (IsOpenGate(mat)) + return true; + if (mat.IsSolid()) + return false; + if (mat.CanHarmPlayers()) + return false; + return true; + } + + /// + /// Can a player stand on top of this block? (solid upper surface) + /// + public static bool CanWalkOn(CalculationContext ctx, int x, int y, int z) + { + Material mat = ctx.GetMaterial(x, y, z); + if (mat == Material.Air || mat == Material.CaveAir || mat == Material.VoidAir) + return false; + if (mat.IsLiquid()) + return false; + if (mat.CanHarmPlayers()) + return false; + if (mat.CanBeClimbedOn()) + return false; + if (IsOpenGate(mat)) + return false; + return mat.IsSolid(); + } + + /// + /// Is this block completely passable with no slowdown or interaction? + /// Stricter than CanWalkThrough -- excludes water, cobwebs, etc. + /// + public static bool IsFullyPassable(CalculationContext ctx, int x, int y, int z) + { + Material mat = ctx.GetMaterial(x, y, z); + return mat == Material.Air || mat == Material.CaveAir || mat == Material.VoidAir; + } + + public static bool IsClimbable(Material mat) + { + return mat.CanBeClimbedOn(); + } + + public static bool IsHazardous(Material mat) + { + return mat.CanHarmPlayers(); + } + + public static bool IsWater(Material mat) + { + return mat == Material.Water; + } + + /// + /// Can the player safely land on this block? True for solid blocks + /// except bottom slabs (which cause glitchy fall damage in vanilla). + /// + public static bool CanSafelyLandOn(CalculationContext ctx, int x, int y, int z) + { + if (!CanWalkOn(ctx, x, y, z)) + return false; + // TODO: detect bottom slabs via BlockShapes and reject them + // (Baritone rejects bottom slab landings due to unreliable fall damage) + return true; + } + + /// + /// Does this block absorb/negate fall damage? + /// Water, slime blocks, hay bales, and powder snow reduce or eliminate fall damage. + /// + public static bool AbsorbsFallDamage(Material mat) + { + return mat is Material.Water or Material.SlimeBlock + or Material.HayBlock or Material.PowderSnow; + } + + /// + /// Conservative check for gate-type blocks. Since we cannot read block state + /// (open/closed) during planning, treat all fence gates as passable. + /// + private static bool IsOpenGate(Material mat) + { + return mat is Material.AcaciaFenceGate or Material.BirchFenceGate + or Material.CrimsonFenceGate or Material.DarkOakFenceGate + or Material.JungleFenceGate or Material.MangroveFenceGate + or Material.OakFenceGate or Material.SpruceFenceGate + or Material.WarpedFenceGate or Material.CherryFenceGate + or Material.BambooFenceGate or Material.PaleOakFenceGate; + } + } +} diff --git a/MinecraftClient/Pathing/Moves/ParkourFeasibility.cs b/MinecraftClient/Pathing/Moves/ParkourFeasibility.cs new file mode 100644 index 0000000000..2f398e3e2c --- /dev/null +++ b/MinecraftClient/Pathing/Moves/ParkourFeasibility.cs @@ -0,0 +1,410 @@ +using System; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves; + +internal static class ParkourFeasibility +{ + public static bool IsSidewallProfile(int xOffset, int zOffset, int yDelta) + { + int absX = Math.Abs(xOffset); + int absZ = Math.Abs(zOffset); + int major = Math.Max(absX, absZ); + int minor = Math.Min(absX, absZ); + + return minor == 1 + && major >= 2 + && major <= 5 + && yDelta is >= -2 and <= 1; + } + + public static void GetSidewallAxes(int xOffset, int zOffset, out int forwardX, out int forwardZ, out int lateralX, out int lateralZ) + { + if (Math.Abs(xOffset) > Math.Abs(zOffset)) + { + forwardX = Math.Sign(xOffset); + forwardZ = 0; + lateralX = 0; + lateralZ = Math.Sign(zOffset); + } + else + { + forwardX = 0; + forwardZ = Math.Sign(zOffset); + lateralX = Math.Sign(xOffset); + lateralZ = 0; + } + } + + public static bool HasRunUp( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset, + int yDelta) + { + double horiz = Math.Sqrt(xOffset * xOffset + zOffset * zOffset); + bool carriedEntry = ctx.PreviousMoveType is MoveType.Parkour or MoveType.Descend; + + // Sprint-momentum carry semantics for flat (yDelta == 0) parkour: + // * Parkour-carry: the previous move's airborne sprint is preserved + // cleanly through landing, so a chained 5 c2c flat parkour can + // fire immediately from the takeoff edge. + // * Descend-carry: the previous Descend often overshoots the + // takeoff block by ~0.5 m (the bot lands inside the takeoff + // block but already past the leading edge), eating the runway + // the SprintJumpTemplate needs to spin sprint back up. In + // practice this lets the bot launch with sub-12-tick momentum + // and short-fall the 5 c2c gap by ~0.7 m. Treat Descend-carry + // as a cold start for flat run-up so the planner inserts an + // explicit traverse runway or picks a shorter parkour. + bool parkourCarry = ctx.PreviousMoveType == MoveType.Parkour; + double threshold = yDelta switch + { + > 0 when carriedEntry => 4.5, + > 0 => 2.5, + < 0 when carriedEntry => 5.5, + < 0 => 3.5, + _ when parkourCarry => 5.5, + _ => 3.5, + }; + if (horiz < threshold) + return true; + + if (carriedEntry && yDelta < 0) + return true; + + int xSign = Math.Sign(xOffset); + int zSign = Math.Sign(zOffset); + + // Long flat sprint parkour (5 c2c, horiz~5) requires the player to be + // launched at full vanilla sprint velocity (~12 momentum ticks). A + // standing-jump cold start only reaches gap=3 (=4 c2c). Without a + // clean Parkour-carry, one back block of runway is not enough to + // spin sprint up; demand at least two aligned back blocks so the + // executor has a real run-up window. + // tools/sim_jump_reach.py "Standing sprint jump (0t momentum)" matrix + // shows gap=4 dy=0 is unreachable, while 12t-momentum gap=4 reaches + // 5.1075 m. + int requiredBackBlocks = (yDelta == 0 && !parkourCarry && horiz >= 4.5) ? 2 : 1; + + for (int i = 1; i <= requiredBackBlocks; i++) + { + int backX = x - xSign * i; + int backZ = z - zSign * i; + if (!ctx.CanWalkOn(backX, y - 1, backZ)) + return false; + if (!IsColumnPassable(ctx, backX, y, backZ)) + return false; + } + + return true; + } + + public static bool TryGetRequiredStaticEntryRunupSteps( + MoveType previousMoveType, + int xOffset, + int zOffset, + int yDelta, + out int requiredSteps) + { + requiredSteps = 0; + + if (previousMoveType is MoveType.Parkour or MoveType.Descend) + return false; + + int major = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + if (yDelta == -1 && major == 5) + { + requiredSteps = 1; + return true; + } + + return false; + } + + public static bool HasPreparedRunup( + EntryPreparationState state, + int x, + int y, + int z, + int forwardX, + int forwardZ, + int requiredSteps) + { + return state.Kind == EntryPreparationKind.SidewallRunup + && state.IsPrepared + && state.OriginX == x + && state.OriginY == y + && state.OriginZ == z + && state.ForwardX == forwardX + && state.ForwardZ == forwardZ + && state.RequiredSteps == requiredSteps; + } + + public static bool HasDiagonalShoulderClearance( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset) + { + if (xOffset == 0 || zOffset == 0) + return true; + + return IsColumnPassable(ctx, x + Math.Sign(xOffset), y, z) + && IsColumnPassable(ctx, x, y, z + Math.Sign(zOffset)); + } + + public static bool HasLandingOvershootClearance( + CalculationContext ctx, + int destX, + int destY, + int destZ, + int xSign, + int zSign) + { + // The original check rejected jumps whose landing column had a wall + // immediately past it (on the same axis as the takeoff). Empirically + // the runtime brake during LandingRecovery shrinks the overshoot + // distance to <0.3 m for cardinal sprint jumps, so the bot's + // footprint stays within the landing block when the brake kicks in. + // Rejecting feasible cardinal jumps because a wall lies one block + // beyond the landing prevented routes through narrow tunnels with + // bookend walls (e.g. a 2 c2c +1 ascend out of a dead-end alcove). + // Defer to the executor's deceleration profile and accept the move. + _ = ctx; + _ = destX; + _ = destY; + _ = destZ; + _ = xSign; + _ = zSign; + return true; + } + + public static bool HasCardinalSideClearance( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset) + { + if ((xOffset == 0) == (zOffset == 0)) + return true; + + // For a cardinal sprint jump the bot's footprint (0.6 m wide centred + // on the takeoff/landing axis) stays at least 0.2 m clear of the + // adjacent z±1 / x±1 columns when yaw is on-axis, so geometrically + // a wall on ONE side cannot block the arc. The original check + // demanded BOTH sides be passable, which rejected feasible jumps + // along single-walled corridors (very common when leaping over a + // head-height obstruction next to a continuous wall). + // + // Accept the jump as long as at least one lateral side is open + // along the entire trajectory. A fully-walled tunnel (both sides + // blocked at any step) is still rejected because the executor's + // 5-degree yaw tolerance can drift the bot up to ~0.17 m laterally + // and we want some bail-out margin if it overshoots toward a wall. + if (xOffset != 0) + { + int xSign = Math.Sign(xOffset); + for (int step = 1; step <= Math.Abs(xOffset); step++) + { + int gx = x + xSign * step; + if (!IsColumnPassable(ctx, gx, y, z - 1) + && !IsColumnPassable(ctx, gx, y, z + 1)) + { + return false; + } + } + + return true; + } + + int zSign = Math.Sign(zOffset); + for (int step = 1; step <= Math.Abs(zOffset); step++) + { + int gz = z + zSign * step; + if (!IsColumnPassable(ctx, x - 1, y, gz) + && !IsColumnPassable(ctx, x + 1, y, gz)) + { + return false; + } + } + + return true; + } + + public static bool HasIntermediateLandingConflict( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset, + int yDelta) + { + if (yDelta >= 0) + return false; + + bool cardinal = (xOffset == 0) != (zOffset == 0); + int distance = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + if (!cardinal || distance < 6) + return false; + + int destY = y + yDelta; + int xSign = Math.Sign(xOffset); + int zSign = Math.Sign(zOffset); + + for (int step = 1; step < distance; step++) + { + int gx = x + (xOffset != 0 ? xSign * step : 0); + int gz = z + (zOffset != 0 ? zSign * step : 0); + + for (int candidateY = y - 1; candidateY >= destY; candidateY--) + { + if (ctx.CanWalkOn(gx, candidateY - 1, gz) && IsColumnPassable(ctx, gx, candidateY, gz)) + return true; + } + } + + return false; + } + + public static bool HasDominantAxisRunUp( + CalculationContext ctx, + int x, + int y, + int z, + int forwardX, + int forwardZ, + int xOffset, + int zOffset, + int yDelta) + { + int major = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + int maxMajor = yDelta switch + { + > 0 => 3, + < 0 => 5, + _ => 4, + }; + + if (major > maxMajor) + return false; + + bool carriedEntry = ctx.PreviousMoveType is MoveType.Parkour or MoveType.Descend; + if (carriedEntry) + return true; + + // Cold-start sprint-jump reaches ~3.1-3.5 blocks horizontally without + // any pre-existing momentum, so short sidewall jumps remain feasible + // from a lone overhang block even when no 2-block runway is available + // behind the start (matches the staircase/step-pyramid cases seen in + // the wild, and Baritone's MomentumBehavior.ALLOWED contract). + double horiz = Math.Sqrt((xOffset * xOffset) + (zOffset * zOffset)); + double coldStartReach = yDelta switch + { + > 0 => 2.5, + < 0 => 3.3, + _ => 3.2, + }; + if (horiz <= coldStartReach) + return true; + + for (int i = 1; i <= 2; i++) + { + int rx = x - (forwardX * i); + int rz = z - (forwardZ * i); + if (!ctx.CanWalkOn(rx, y - 1, rz) || !IsColumnPassable(ctx, rx, y, rz)) + return false; + } + + return true; + } + + public static bool HasSidewallArcClearance( + CalculationContext ctx, + int x, + int y, + int z, + int forwardX, + int forwardZ, + int lateralX, + int lateralZ, + int xOffset, + int zOffset, + int yDelta) + { + int major = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + int insideWallDepth = 0; + + // Probe up to MaxProbeDepth cells along the forward axis at the lateral + // column to measure how thick the inner wall is. A 1- or 2-thick wall + // was the original supported case; thicker walls (3) still let the + // sidewall arc play out because the wall only provides lateral support + // during the sprint-jump — the player brushes the wall longer but the + // forward reach is unchanged. Walls thicker than MaxProbeDepth are + // rejected because they either bury the landing column or leave no + // open air for the arc to complete. + const int MaxProbeDepth = 3; + for (int step = 0; step < MaxProbeDepth; step++) + { + int wx = x + lateralX + (forwardX * step); + int wz = z + lateralZ + (forwardZ * step); + if (ctx.CanWalkThrough(wx, y, wz) && ctx.CanWalkThrough(wx, y + 1, wz)) + break; + insideWallDepth++; + } + + if (insideWallDepth is < 1 or > MaxProbeDepth) + return false; + + for (int step = 1; step <= major; step++) + { + int cx = x + (forwardX * step); + int cz = z + (forwardZ * step); + if (!IsColumnPassable(ctx, cx, y, cz)) + return false; + } + + int outsideX = x - lateralX; + int outsideZ = z - lateralZ; + return IsColumnPassable(ctx, outsideX, y, outsideZ); + } + + public static bool HasSidewallLandingClearance( + CalculationContext ctx, + int destX, + int destY, + int destZ, + int forwardX, + int forwardZ, + int lateralX, + int lateralZ) + { + if (!ctx.CanWalkOn(destX, destY - 1, destZ)) + return false; + + if (!IsColumnPassable(ctx, destX, destY, destZ)) + return false; + + if (!IsColumnPassable(ctx, destX + forwardX, destY, destZ + forwardZ)) + return false; + + if (!IsColumnPassable(ctx, destX - lateralX, destY, destZ - lateralZ)) + return false; + + return true; + } + + private static bool IsColumnPassable(CalculationContext ctx, int x, int y, int z) + { + return ctx.CanWalkThrough(x, y, z) + && ctx.CanWalkThrough(x, y + 1, z); + } +} diff --git a/MinecraftClient/Physics/CollisionDetector.cs b/MinecraftClient/Physics/CollisionDetector.cs index 0788e93b01..8391dd6468 100644 --- a/MinecraftClient/Physics/CollisionDetector.cs +++ b/MinecraftClient/Physics/CollisionDetector.cs @@ -23,8 +23,8 @@ public static Vec3d Collide(World world, Aabb entityBox, Vec3d movement, bool on var colliders = CollectBlockColliders(world, entityBox.ExpandTowards(movement)); Vec3d resolved = CollideWithShapes(movement, entityBox, colliders); - bool blockedX = movement.X != resolved.X; - bool blockedZ = movement.Z != resolved.Z; + bool blockedX = Math.Abs(movement.X - resolved.X) > 1.0E-5; + bool blockedZ = Math.Abs(movement.Z - resolved.Z) > 1.0E-5; bool blockedY = movement.Y != resolved.Y; bool hitGroundDuringMove = blockedY && movement.Y < 0.0; @@ -59,7 +59,7 @@ public static Vec3d Collide(World world, Aabb entityBox, Vec3d movement, bool on /// /// Collide movement against a list of shapes using axis-separated resolution. - /// Matches Entity.collideWithShapes() — processes axes in order of smallest movement first. + /// Matches Entity.collideWithShapes() with vanilla's axis ordering (Y first, then larger horizontal axis). /// private static Vec3d CollideWithShapes(Vec3d movement, Aabb entityBox, List colliders) { @@ -82,31 +82,14 @@ private static Vec3d CollideWithShapes(Vec3d movement, Aabb entityBox, List - /// Get axis processing order: Y first if moving down, otherwise smallest absolute movement first. - /// Vanilla uses Direction.axisStepOrder(Vec3) which returns axes sorted by absolute movement. + /// Get axis processing order matching vanilla Direction.Axis.axisStepOrder(Vec3): + /// Y is always first, then the larger horizontal axis, then the smaller. /// private static int[] GetAxisStepOrder(Vec3d movement) { - double absX = Math.Abs(movement.X); - double absY = Math.Abs(movement.Y); - double absZ = Math.Abs(movement.Z); - - if (absX > absZ) - { - if (absZ > absY) - return new[] { 1, 2, 0 }; // Y Z X - if (absX > absY) - return new[] { 1, 0, 2 }; // Y X Z - return new[] { 0, 1, 2 }; // X Y Z - } - else - { - if (absX > absY) - return new[] { 1, 0, 2 }; // Y X Z - if (absZ > absY) - return new[] { 1, 2, 0 }; // Y Z X - return new[] { 2, 1, 0 }; // Z Y X - } + return Math.Abs(movement.X) < Math.Abs(movement.Z) + ? [1, 2, 0] // Y Z X + : [1, 0, 2]; // Y X Z } /// diff --git a/MinecraftClient/Physics/PhysicsConsts.cs b/MinecraftClient/Physics/PhysicsConsts.cs index 95cb02c1fd..c2b32b13bf 100644 --- a/MinecraftClient/Physics/PhysicsConsts.cs +++ b/MinecraftClient/Physics/PhysicsConsts.cs @@ -1,3 +1,5 @@ +using System; + namespace MinecraftClient.Physics { /// @@ -6,12 +8,19 @@ namespace MinecraftClient.Physics /// public static class PhysicsConsts { - // --- Player dimensions --- + // --- Player dimensions per pose (vanilla Avatar.POSES, 26.1) --- public const double PlayerWidth = 0.6; - public const double PlayerHeight = 1.8; - public const double PlayerSneakHeight = 1.5; - public const double PlayerSwimHeight = 0.6; - public const double PlayerEyeHeight = 1.62; + public const double PlayerStandingHeight = 1.8; + public const double PlayerStandingEyeHeight = 1.62; + public const double PlayerCrouchingHeight = 1.5; + public const double PlayerCrouchingEyeHeight = 1.27; + public const double PlayerSwimmingHeight = 0.6; + public const double PlayerSwimmingEyeHeight = 0.4; + + [Obsolete("Use PlayerStandingHeight instead")] + public const double PlayerHeight = PlayerStandingHeight; + [Obsolete("Use PlayerStandingEyeHeight instead")] + public const double PlayerEyeHeight = PlayerStandingEyeHeight; // --- Gravity --- public const double DefaultGravity = 0.08; diff --git a/MinecraftClient/Physics/PlayerPhysics.cs b/MinecraftClient/Physics/PlayerPhysics.cs index bf18429f46..23d5337817 100644 --- a/MinecraftClient/Physics/PlayerPhysics.cs +++ b/MinecraftClient/Physics/PlayerPhysics.cs @@ -4,9 +4,9 @@ namespace MinecraftClient.Physics { /// - /// Core physics tick engine for the player, faithfully replicating vanilla 1.21.11 physics. + /// Core physics tick engine for the player, faithfully replicating vanilla 1.21.11+ physics. /// Mirrors the combined logic of Entity.move(), LivingEntity.aiStep()/travel()/travelInAir(), - /// Player.travel(), and LocalPlayer.aiStep(). + /// Player.travel(), Player.updatePlayerPose(), and LocalPlayer.aiStep(). /// public class PlayerPhysics { @@ -33,15 +33,34 @@ public class PlayerPhysics public bool Sneaking; public bool CreativeFlying; public bool InWater; + public bool IsUnderWater; public bool InLava; public bool OnClimbable; public bool HasSlowFalling; public bool HasLevitation; public int LevitationAmplifier; - // Player dimensions - public double PlayerWidth = PhysicsConsts.PlayerWidth; - public double PlayerHeight = PhysicsConsts.PlayerHeight; + // --- Pose system (vanilla Player.updatePlayerPose / Avatar.POSES) --- + public EntityPose CurrentPose { get; private set; } = EntityPose.Standing; + private EntityPose previousPose = EntityPose.Standing; + + public double PlayerWidth => PhysicsConsts.PlayerWidth; + + public double PlayerHeight => CurrentPose switch + { + EntityPose.Sneaking => PhysicsConsts.PlayerCrouchingHeight, + EntityPose.Swimming or EntityPose.FallFlying or EntityPose.SpinAttack + => PhysicsConsts.PlayerSwimmingHeight, + _ => PhysicsConsts.PlayerStandingHeight + }; + + public double EyeHeight => CurrentPose switch + { + EntityPose.Sneaking => PhysicsConsts.PlayerCrouchingEyeHeight, + EntityPose.Swimming or EntityPose.FallFlying or EntityPose.SpinAttack + => PhysicsConsts.PlayerSwimmingEyeHeight, + _ => PhysicsConsts.PlayerStandingEyeHeight + }; // Anti-jump-spam private int noJumpDelay; @@ -52,6 +71,11 @@ public class PlayerPhysics // Movement speed attribute (base = 0.1 for players) public float MovementSpeed = 0.1f; + /// + /// Debug log callback. Set from McClient to route messages through MCC's logger. + /// + public Action? DebugLog; + /// /// Get the player's bounding box at current position /// @@ -67,6 +91,9 @@ public void Tick(World world) { TickCount++; + // Update pose (vanilla Player.updatePlayerPose) + UpdatePlayerPose(world); + // Velocity threshold zeroing (LivingEntity.aiStep) ZeroTinyVelocity(); @@ -81,6 +108,14 @@ public void Tick(World world) if (noJumpDelay > 0) noJumpDelay--; + + // Periodic state dump every 5 seconds (100 ticks) + if (DebugLog is not null && TickCount % 100 == 0) + { + DebugLog($"[Physics] tick={TickCount} pos={Position} vel={DeltaMovement} " + + $"ground={OnGround} pose={CurrentPose} fall={FallDistance:F2} " + + $"water={InWater} underwater={IsUnderWater} swim={IsSwimming()} sneak={Sneaking}"); + } } /// @@ -240,6 +275,9 @@ private void TravelInAir(World world, Vec3d input) // Block speed factor (soul sand, honey, etc.) ApplyBlockSpeedFactor(world); + + // SlimeBlock.stepOn: slow horizontal movement when walking on slime + ApplySlimeStepOn(world); } /// @@ -353,12 +391,6 @@ private void Move(World world, Vec3d movement) double resolvedLenSqr = resolved.LengthSqr(); if (resolvedLenSqr > 1.0E-7 || movement.LengthSqr() - resolvedLenSqr < 1.0E-7) { - // Fall distance reset via trace (simplified: reset on hitting ground) - if (FallDistance != 0.0 && resolvedLenSqr >= 1.0) - { - // Simplified: just check vertical collision - } - Position = Position.Add(resolved); } @@ -385,13 +417,46 @@ private void Move(World world, Vec3d movement) blockedZ ? 0 : DeltaMovement.Z); } + // Vanilla: Block.updateEntityMovementAfterFallOn -> SlimeBlock.bounceUp if (VerticalCollision) + UpdateMovementAfterFallOn(world); + } + + /// + /// Vanilla Block.updateEntityMovementAfterFallOn / SlimeBlock.bounceUp. + /// Called when vertical collision is detected. Handles slime block bounce. + /// + private void UpdateMovementAfterFallOn(World world) + { + Location belowFeet = new(Position.X, Position.Y - 0.2, Position.Z); + Material landedOn = world.GetBlock(belowFeet).Type; + + if (landedOn == Material.SlimeBlock && !IsSuppressingBounce()) + { + double vy = DeltaMovement.Y; + if (vy < 0.0) + { + // LivingEntity bounce factor = 1.0 + DeltaMovement = new Vec3d(DeltaMovement.X, -vy, DeltaMovement.Z); + DebugLog?.Invoke($"[Physics] Slime bounce! vy={vy:F4} -> {-vy:F4} at {Position}"); + } + else + { + DeltaMovement = new Vec3d(DeltaMovement.X, 0, DeltaMovement.Z); + } + } + else { - // Slime block bounce would go here; for now just zero Y + // Default: zero vertical velocity DeltaMovement = new Vec3d(DeltaMovement.X, 0, DeltaMovement.Z); } } + /// + /// Vanilla Entity.isSuppressingBounce() - sneaking suppresses slime bounce. + /// + private bool IsSuppressingBounce() => Sneaking; + /// /// Sneak edge detection: prevent walking off edges while sneaking. /// Equivalent to Player.maybeBackOffFromEdge(Vec3, MoverType). @@ -464,16 +529,23 @@ private double GetEffectiveGravity() /// private float GetFrictionInfluencedSpeed(float friction) { + float effectiveSpeed = GetEffectiveMovementSpeed(); if (OnGround) { - return MovementSpeed * (PhysicsConsts.GroundAccelerationFactor / (friction * friction * friction)); + return effectiveSpeed * (PhysicsConsts.GroundAccelerationFactor / (friction * friction * friction)); } else { - return CreativeFlying ? MovementSpeed * 0.1f : PhysicsConsts.AirAcceleration; + return CreativeFlying ? effectiveSpeed * 0.1f : effectiveSpeed * 0.2f; } } + private float GetEffectiveMovementSpeed() + { + // Vanilla applies a transient +30% total movement-speed modifier while sprinting. + return Sprinting ? MovementSpeed * 1.3f : MovementSpeed; + } + /// /// Get the friction of the block below the player's feet. /// @@ -507,6 +579,26 @@ private void ApplyBlockSpeedFactor(World world) } } + /// + /// Vanilla SlimeBlock.stepOn: reduces horizontal speed when walking on slime blocks. + /// Triggered when vertical velocity is small and player is not sneaking. + /// + private void ApplySlimeStepOn(World world) + { + if (!OnGround) return; + + Location belowFeet = new(Position.X, Position.Y - 0.5000010, Position.Z); + if (world.GetBlock(belowFeet).Type != Material.SlimeBlock) return; + + double absDeltaY = Math.Abs(DeltaMovement.Y); + if (absDeltaY >= 0.1 || Sneaking) return; + + double scale = 0.4 + absDeltaY * 0.2; + DeltaMovement = DeltaMovement.Multiply(scale, 1.0, scale); + + DebugLog?.Invoke($"[Physics] Slime stepOn slowdown: scale={scale:F3}, vel={DeltaMovement}"); + } + /// /// Get friction value for a material. Default 0.6, special blocks differ. /// @@ -549,10 +641,84 @@ public void UpdateEnvironment(World world) InWater = feetBlock == Material.Water || headBlock == Material.Water || feetBlock == Material.BubbleColumn; + IsUnderWater = headBlock == Material.Water; InLava = feetBlock == Material.Lava || headBlock == Material.Lava; OnClimbable = feetBlock.CanBeClimbedOn(); } + // ==================== Pose System ==================== + + /// + /// Vanilla Player.updatePlayerPose(). + /// Determines the correct pose based on player state and space constraints. + /// Forces crawling (Swimming pose on land) when standing/crouching does not fit. + /// + private void UpdatePlayerPose(World world) + { + EntityPose desired = GetDesiredPose(); + EntityPose actual; + + if (CanPlayerFitWithPose(world, EntityPose.Swimming)) + { + if (CanPlayerFitWithPose(world, desired)) + actual = desired; + else if (CanPlayerFitWithPose(world, EntityPose.Sneaking)) + actual = EntityPose.Sneaking; + else + actual = EntityPose.Swimming; + } + else + { + actual = desired; + } + + if (actual != previousPose) + { + DebugLog?.Invoke($"[Physics] Pose: {previousPose} -> {actual} (desired={desired}, " + + $"height={GetHeightForPose(actual):F1}, pos={Position})"); + previousPose = actual; + } + + CurrentPose = actual; + } + + /// + /// Vanilla Player.getDesiredPose() -- determines what pose the player wants. + /// + private EntityPose GetDesiredPose() + { + if (IsSwimming()) + return EntityPose.Swimming; + if (Sneaking && !CreativeFlying) + return EntityPose.Sneaking; + return EntityPose.Standing; + } + + /// + /// Vanilla Entity.isSwimming() for players: sprinting underwater and not flying. + /// + private bool IsSwimming() => !CreativeFlying && Sprinting && IsUnderWater; + + /// + /// Check if the player can fit at current position with the given pose's dimensions. + /// Vanilla Player.canPlayerFitWithinBlocksAndEntitiesWhen(Pose). + /// + private bool CanPlayerFitWithPose(World world, EntityPose pose) + { + double height = GetHeightForPose(pose); + Aabb box = Aabb.OfSize(Position.X, Position.Y, Position.Z, PlayerWidth, height); + Aabb deflated = box.Deflate(1.0E-7, 1.0E-7, 1.0E-7); + return CollisionDetector.NoCollision(world, deflated); + } + + private static double GetHeightForPose(EntityPose pose) => pose switch + { + EntityPose.Sneaking => PhysicsConsts.PlayerCrouchingHeight, + EntityPose.Swimming or EntityPose.FallFlying or EntityPose.SpinAttack + => PhysicsConsts.PlayerSwimmingHeight, + _ => PhysicsConsts.PlayerStandingHeight + }; + /// /// Set position from server teleport / initial spawn. /// diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index b6e454cdc7..11a851ae63 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -3501,6 +3501,141 @@ internal static string cmd_exit_desc { } } + /// + /// Looks up a localized string similar to navigate to a location using A* pathfinding.. + /// + internal static string cmd_goto_desc { + get { + return ResourceManager.GetString("cmd.goto.desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path found: {0} waypoints, {1} nodes explored in {2}ms{3}. + /// + internal static string cmd_goto_success { + get { + return ResourceManager.GetString("cmd.goto.success", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No path found ({0} nodes explored in {1}ms). + /// + internal static string cmd_goto_failed { + get { + return ResourceManager.GetString("cmd.goto.failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Planning path in background (budget: {0}ms)... result will appear in the log. + /// + internal static string cmd_goto_planning { + get { + return ResourceManager.GetString("cmd.goto.planning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to toggle verbose pathing diagnostics (full plan dump, failure trace). + /// + internal static string cmd_pathdiag_desc { + get { + return ResourceManager.GetString("cmd.pathdiag.desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pathing diagnostics enabled. Run /pathdiag off to disable. + /// + internal static string cmd_pathdiag_enabled { + get { + return ResourceManager.GetString("cmd.pathdiag.enabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pathing diagnostics disabled. + /// + internal static string cmd_pathdiag_disabled { + get { + return ResourceManager.GetString("cmd.pathdiag.disabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] routeStart segments={0}. + /// + internal static string pathing_metric_route_start { + get { + return ResourceManager.GetString("pathing.metric.route_start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] segmentStart index={0} total={1} move={2} transition={3}. + /// + internal static string pathing_metric_segment_start { + get { + return ResourceManager.GetString("pathing.metric.segment_start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] segmentComplete index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2}. + /// + internal static string pathing_metric_segment_complete { + get { + return ResourceManager.GetString("pathing.metric.segment_complete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] segmentFailed index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2}. + /// + internal static string pathing_metric_segment_failed { + get { + return ResourceManager.GetString("pathing.metric.segment_failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] routeComplete totalTicks={0}. + /// + internal static string pathing_metric_route_complete { + get { + return ResourceManager.GetString("pathing.metric.route_complete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] replanStart count={0} x={1:F2} y={2:F2} z={3:F2}. + /// + internal static string pathing_metric_replan_start { + get { + return ResourceManager.GetString("pathing.metric.replan_start", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] replanSuccess count={0} segments={1}. + /// + internal static string pathing_metric_replan_success { + get { + return ResourceManager.GetString("pathing.metric.replan_success", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [PathMetric] replanFailed count={0} x={1:F2} y={2:F2} z={3:F2}. + /// + internal static string pathing_metric_replan_failed { + get { + return ResourceManager.GetString("pathing.metric.replan_failed", resourceCulture); + } + } + /// /// Looks up a localized string similar to Already following {0}!. /// @@ -4585,6 +4720,24 @@ internal static string cmd_recipebook_unsupported { } } + /// + /// Looks up a localized string similar to Use new A* pathfinding to navigate to a location.. + /// + internal static string cmd_pathfind_desc { + get { + return ResourceManager.GetString("cmd.pathfind.desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pathfinding to ({0}, {1}, {2}).... + /// + internal static string cmd_pathfind_started { + get { + return ResourceManager.GetString("cmd.pathfind.started", resourceCulture); + } + } + /// /// Looks up a localized string similar to restart and reconnect to the server.. /// @@ -6671,6 +6824,15 @@ internal static string cmd_debug_state_location { } } + /// + /// Looks up a localized string similar to OnGround. + /// + internal static string cmd_debug_state_on_ground { + get { + return ResourceManager.GetString("cmd.debug.state_on_ground", resourceCulture); + } + } + /// /// Looks up a localized string similar to TPS. /// diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index 2e9da34af4..e3b34fc63a 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -1243,6 +1243,51 @@ Change EnableEmoji=false in the settings if the display is confusing. disconnect from the server. + + navigate to a location using A* pathfinding. + + + Path found: {0} waypoints, {1} nodes explored in {2}ms{3} + + + No path found ({0} nodes explored in {1}ms) + + + Planning path in background (budget: {0}ms)... result will appear in the log. + + + toggle verbose pathing diagnostics (full plan dump, failure trace). + + + Pathing diagnostics enabled. Run /pathdiag off to disable. + + + Pathing diagnostics disabled. + + + [PathMetric] routeStart segments={0} + + + [PathMetric] segmentStart index={0} total={1} move={2} transition={3} + + + [PathMetric] segmentComplete index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2} + + + [PathMetric] segmentFailed index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2} + + + [PathMetric] routeComplete totalTicks={0} + + + [PathMetric] replanStart count={0} x={1:F2} y={2:F2} z={3:F2} + + + [PathMetric] replanSuccess count={0} segments={1} + + + [PathMetric] replanFailed count={0} x={1:F2} y={2:F2} z={3:F2} + Already following {0}! @@ -1538,6 +1583,12 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s Walking from {1} to {0} + + Use new A* pathfinding to navigate to a location. + + + Pathfinding to ({0}, {1}, {2})... + restart and reconnect to the server. @@ -2355,6 +2406,9 @@ Logging in... Location + + OnGround + TPS diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 45236fdacd..851f22b523 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -1246,6 +1246,18 @@ protected bool MoveToLocation(Location location, bool allowUnsafe = false, bool return Handler.MoveTo(location, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout); } + /// + /// Navigate to a goal using A* pathfinding with template-based execution. + /// Supports GoalBlock, GoalXZ, GoalNear, GoalComposite for flexible targeting. + /// + /// Target goal (GoalBlock, GoalNear, GoalXZ, etc.) + /// Maximum pathfinding computation time in milliseconds + /// Tuple of (success, descriptive message) + protected (bool success, string message) NavigateTo(Pathing.Goals.IGoal goal, long timeoutMs = 5000) + { + return Handler.NavigateToGoal(goal, timeoutMs); + } + /// /// Check if the client is currently processing a Movement. /// @@ -1255,6 +1267,24 @@ protected bool ClientIsMoving() return Handler.ClientIsMoving(); } + /// + /// Cancel the current movement, stopping both legacy and A* pathfinding. + /// + /// true if there was an active movement that was cancelled + protected bool CancelMovement() + { + return Handler.CancelMovement(); + } + + /// + /// Get the current movement goal location. + /// Returns Location.Zero if no movement is active. + /// + protected Location GetCurrentMovementGoal() + { + return Handler.GetCurrentMovementGoal(); + } + /// /// Look at the specified location /// diff --git a/config/phase0_test.cs b/config/phase0_test.cs new file mode 100644 index 0000000000..c520fd1fdf --- /dev/null +++ b/config/phase0_test.cs @@ -0,0 +1,121 @@ +//MCCScript 1.0 + +MCC.LoadBot(new Phase0Test()); + +//MCCScript Extensions + +public class Phase0Test : ChatBot +{ + private int phase = 0; + private int ticksInPhase = 0; + + public override void Initialize() + { + LogToConsole("=== Phase 0 Physics Test ==="); + } + + public override void AfterGameJoined() + { + LogToConsole("Joined. Starting tests..."); + } + + public override void Update() + { + ticksInPhase++; + + switch (phase) + { + case 0: // Setup area + if (ticksInPhase == 1) + { + LogToConsole("[Setup] Creating test area at spawn..."); + SendText("/tp @s 0 80 0"); + } + if (ticksInPhase == 40) + SendText("/fill -5 79 -5 15 79 15 stone"); + if (ticksInPhase == 50) + SendText("/fill -5 80 -5 15 85 15 air"); + if (ticksInPhase == 60) + SendText("/tp @s 0 80 0"); + if (ticksInPhase >= 80) NextPhase(); + break; + + case 1: // Test crawling: place 1-block-high ceiling + if (ticksInPhase == 1) + { + LogToConsole("[Test 1] CRAWLING - Placing ceiling at y=81 above player (1 block headroom)"); + SendText("/setblock 0 81 0 stone"); + } + if (ticksInPhase == 40) + { + var loc = GetCurrentLocation(); + LogToConsole("[Test 1] Pos: " + loc + " - Check debug log for Swimming/crawl pose"); + } + if (ticksInPhase == 80) + { + LogToConsole("[Test 1] Removing ceiling..."); + SendText("/setblock 0 81 0 air"); + } + if (ticksInPhase == 100) + { + var loc = GetCurrentLocation(); + LogToConsole("[Test 1] After removal pos: " + loc + " - Should be back to Standing"); + } + if (ticksInPhase >= 120) NextPhase(); + break; + + case 2: // Test slime bounce (no sneak) + if (ticksInPhase == 1) + { + LogToConsole("[Test 2] SLIME BOUNCE - Placing slime blocks and falling"); + SendText("/fill 8 79 0 10 79 2 slime_block"); + } + if (ticksInPhase == 20) + { + LogToConsole("[Test 2] Teleporting 10 blocks above slime..."); + SendText("/tp @s 9 90 1"); + } + if (ticksInPhase % 10 == 0 && ticksInPhase >= 30 && ticksInPhase <= 100) + { + var loc = GetCurrentLocation(); + LogToConsole("[Test 2] tick=" + ticksInPhase + " Pos: " + loc); + } + if (ticksInPhase >= 160) NextPhase(); + break; + + case 3: // Test sneaking (move with sneak) + if (ticksInPhase == 1) + { + LogToConsole("[Test 3] SNEAK MOVEMENT"); + SendText("/tp @s 0 80 0"); + } + if (ticksInPhase == 30) + { + LogToConsole("[Test 3] Moving to (5,80,0) with unsafe path..."); + MoveToLocation(new Location(5, 80, 0), allowUnsafe: true); + } + if (ticksInPhase % 10 == 0 && ticksInPhase >= 30 && ticksInPhase <= 80) + { + var loc = GetCurrentLocation(); + LogToConsole("[Test 3] tick=" + ticksInPhase + " Pos: " + loc); + } + if (ticksInPhase >= 100) NextPhase(); + break; + + case 4: // Done + if (ticksInPhase == 1) + { + LogToConsole("=== Phase 0 Tests Complete ==="); + LogToConsole("Check debug log for [Physics] messages."); + UnloadBot(); + } + break; + } + } + + private void NextPhase() + { + phase++; + ticksInPhase = 0; + } +} diff --git a/docs/guide/ai-assisted-development.md b/docs/guide/ai-assisted-development.md index 11686d64c9..fa94e552e8 100644 --- a/docs/guide/ai-assisted-development.md +++ b/docs/guide/ai-assisted-development.md @@ -335,12 +335,12 @@ git submodule update --init --recursive From the repo root, use the decompiler helper to download the official server jar and create the decompiled source tree: ```bash -tools/decompile.sh --version 1.20.6 +tools/decompile.sh --version 1.20.6-Vanilla ``` That creates the paths used by the harness and the version-adaptation workflow: -- `$MCC_SERVERS/1.20.6/server.jar` +- `$MCC_SERVERS/1.20.6-Vanilla/server.jar` - `MinecraftOfficial/1.20.6-decompiled/` If you are doing protocol work, this step is not optional. @@ -458,13 +458,13 @@ Two worktrees can share one local server like this: # worktree A cd ~/Minecraft/Minecraft-Console-Client source tools/mcc-env.sh -mc-start 1.21.11 -mcc-debug -v 1.21.11 --file-input +mc-start 1.21.11-Vanilla +mcc-debug -v 1.21.11-Vanilla --file-input # worktree B cd ~/Minecraft/Minecraft-Console-Client-foo source tools/mcc-env.sh -mcc-debug -v 1.21.11 --file-input +mcc-debug -v 1.21.11-Vanilla --file-input # from each worktree, mcc-* targets that worktree's default session mcc-state @@ -544,13 +544,13 @@ This is the core loop you should expect an agent to follow. source tools/mcc-env.sh SESSION="smoke-a" USERNAME="$(_mcc_resolve_username "$SESSION")" -mc-start 1.20.6 +mc-start 1.20.6-Vanilla ``` Check the recent server output: ```bash -mc-log 1.20.6 +mc-log 1.20.6-Vanilla ``` ### 2. Build MCC @@ -562,7 +562,7 @@ mcc-build ### 3. Run MCC with file input enabled ```bash -mcc-debug -v 1.20.6 --file-input --session "$SESSION" --no-build +mcc-debug -v 1.20.6-Vanilla --file-input --session "$SESSION" --no-build ``` ### 4. Set up server state through RCON @@ -646,6 +646,7 @@ Server settings that matter for AI-driven offline testing: - `eula=true` - `online-mode=false` +- `difficulty=peaceful` - `enforce-secure-profile=false` - `enable-rcon=true` - `rcon.password=test123` @@ -664,7 +665,7 @@ The important rule is simple: The usual order is: -1. `tools/decompile.sh --version ` +1. `tools/decompile.sh --version -Vanilla` 2. generate server reports from `server.jar` 3. run `tools/diff_registries.py` 4. regenerate the palettes that actually changed @@ -691,9 +692,9 @@ Typical loop: source tools/mcc-env.sh SESSION="smoke-a" USERNAME="$(_mcc_resolve_username "$SESSION")" -mc-start 1.20.6 +mc-start 1.20.6-Vanilla mcc-build -mcc-debug -v 1.20.6 --file-input --session "$SESSION" --no-build +mcc-debug -v 1.20.6-Vanilla --file-input --session "$SESSION" --no-build mc-rcon "op $USERNAME" mcc-cmd --session "$SESSION" "inventory player list" mcc-cmd --session "$SESSION" "entity" @@ -736,7 +737,7 @@ Use skills: Typical flow: ```bash -tools/decompile.sh --version 26.1 +tools/decompile.sh --version 26.1-Vanilla ``` Generate server reports: diff --git a/docs/guide/pathfinding-research.md b/docs/guide/pathfinding-research.md new file mode 100644 index 0000000000..926e7bb0e9 --- /dev/null +++ b/docs/guide/pathfinding-research.md @@ -0,0 +1,359 @@ +# Pathfinding Research: Blip-Up Mechanism and Jump Mechanics + +## Background + +During research for the MCC pathfinding rewrite, we investigated advanced parkour +mechanics in Minecraft Java Edition to determine which movement patterns the new +system should support. + +## Blip-Up Mechanism + +### What is it + +Blip-Up is a physics exploit caused by the **Step-Assist (Stepping)** system +interacting incorrectly with airborne landing. It allows the player to "land" +above ground level and immediately jump again, achieving heights that would +normally be impossible. + +### How Step-Assist works (normal case) + +When the player walks into an obstacle shorter than 0.6 blocks while on the +ground, the game automatically steps the player over it: + +1. Reset the player bounding box to the **position at the start of the tick** +2. Raise the bounding box up by at most 0.6 blocks +3. Move the bounding box horizontally (X axis first, then Z) +4. Lower the bounding box back down by at most 0.6 blocks +5. Compare with the non-stepped movement; keep whichever achieves greater + horizontal distance + +### How Blip-Up exploits it + +The critical flaw: step 1 resets the bounding box to the position at the +**start of the tick**, not after landing. If the player was airborne at the +start of the tick but lands during that tick's collision resolution, the +stepping procedure initiates from the **airborne position** (higher than +the ground). The bounding box may not get lowered enough, causing the +player to "land" mid-air while `onGround` is set to `true`. + +Since `onGround = true`, the player can immediately jump again from this +elevated position. + +### Requirements + +- Negative vertical velocity (falling or descending from a jump arc) +- Land next to a wall of relatively low height (lower than the player's + remaining fall distance on that tick) +- The wall triggers step-assist even though it cannot be directly stepped onto + +### Observed test case + +The following sequence was observed in Bedrock Edition testing (which has +similar but not identical stepping behavior): + +1. Player sneaks to the edge of a purple wool block, facing a wall made of + diamond blocks. The wall extends 3 blocks outward from the landing block. + +2. Player positions camera slightly outward and holds forward while sneaking, + reaching the extreme edge of the block. + +3. Player jumps forward. On the landing tick, they collide with both the + purple wool surface and the adjacent wall. + +4. The stepping system triggers at the airborne position, causing the player + to "land" slightly above the actual surface. `onGround` becomes true. + +5. The player immediately jumps again from this elevated position, gaining + enough height to reach the top of the wall. + +Test images show the player at position (-1, 197, 3) initially, climbing +to (-1, 198, 6) and (-1, 197, 7) via two consecutive jumps where normally +only one jump from ground level would not reach the wall top. + +### Version differences + +| Version range | Blip-Up status | Notes | +|---|---|---| +| Pre-1.8 | Works (with caveats) | MC-3337 bug affects stepping under ceilings | +| 1.8.0 | Works | Always lowers bounding box by 0.6b; grinding impossible | +| 1.8.1 - 1.13.x | Works | Each consecutive blip adds ~0.104 blocks height | +| 1.9 - 1.13.x | Works (slightly different) | Jump height increased to 1.252 (from 1.249); each blip adds ~0.121 | +| 1.14+ | **Patched** | Bounding box now lowers to `playerHeight - verticalSpeed` instead of fixed 0.6b | +| 1.14+ | "Normal blip" still works | Standard step-assist onto low obstacles is intentional behavior | + +### Related mechanics + +- **Jump Cancel**: stepping applied to jumping motion instead of landing; + cancels upward momentum on a slab/stair or ceiling, allowing rapid re-jump + for momentum gain (2-tick cycle under trapdoor ceiling) +- **Grinding**: chaining jump cancels to accelerate; "stair grinding" on + stairs or "ceiling grinding" under a low ceiling +- **Normal Blip**: intended behavior where stepping lets you walk onto an + adjacent block of modest height difference + +### Implications for MCC pathfinding + +1. **1.14+ servers (majority of modern servers)**: Blip-Up is patched; the + pathfinding system does **not** need to account for it. Standard step-up + (0.6b max) and normal jump height (1.252b) define the reachable space. + +2. **Pre-1.14 servers**: if Blip-Up support is desired, the physics engine's + `CollisionDetector.Collide()` step-up logic must match the version-specific + behavior precisely. This is deferred to a later phase. + +3. **Jump Cancel / Grinding**: these mechanics could theoretically enable + faster momentum gain, but they require version-specific ceiling heights + and are considered advanced; deferred to later phases. + +4. **Initial scope**: the pathfinding rewrite focuses on standard jump + physics (1.14+), covering flat jumps, sprint jumps (2-4 blocks), + ascend/descend, and neo-style wall jumps that are achievable within + vanilla 1.14+ physics constraints. + +## Jump Reachability Simulation Results + +The simulation script `tools/sim_jump_reach.py` models vanilla 1.14+ physics +tick-by-tick to determine which jump destinations are reachable. All constants +are sourced from `PhysicsConsts.cs` and match vanilla 1.21.x. + +Run with: `python3 tools/sim_jump_reach.py --verbose` + +### Key Physics Constants + +| Parameter | Value | Source | +|---|---|---| +| Player width | 0.6m | Entity bounding box | +| Player height | 1.8m | Standing pose | +| Base jump power | 0.42 m/tick | LivingEntity.jumpFromGround | +| Sprint jump horizontal boost | +0.2 m/tick | Player sprint bonus | +| Gravity | 0.08 m/tick^2 | Entity gravity | +| Air horizontal drag | 0.91x per tick | Friction multiplier | +| Vertical drag | 0.98x per tick | DragY | +| Air acceleration | 0.02 | LivingEntity.getFrictionInfluencedSpeed | +| Max step height | 0.6m | Step-assist | +| Jump apex | ~1.252b | Computed from physics | + +### Jump Apex + +The maximum jump height is ~1.252 blocks regardless of horizontal speed +or momentum. Momentum only affects horizontal distance at the apex: + +| Mode | Momentum | Apex Y | X at Apex | +|---|---|---|---| +| Walk | 0t | 1.2522 | 0.885 | +| Walk | 12t | 1.2522 | 4.729 | +| Sprint | 0t | 1.2522 | 1.846 | +| Sprint | 12t | 1.2522 | 5.689 | + +### Gap Feasibility Matrix (Sprint, 12t Flat Momentum) + +Can the player cross a gap of N blocks to a platform at height offset dy? + +| Gap | dy=+1.0 | dy=+0.5 | dy=0 | dy=-1 | dy=-2 | dy=-3 | dy=-5 | +|---|---|---|---|---|---|---|---| +| 0 | YES | YES | YES | YES | YES | YES | YES | +| 1 | YES | YES | YES | YES | YES | YES | YES | +| 2 | YES | YES | YES | YES | YES | YES | YES | +| 3 | YES | YES | YES | YES | YES | YES | YES | +| 4 | YES | YES | YES | YES | YES | YES | YES | +| 5 | YES | YES | YES | YES | YES | YES | YES | +| 6 | no | YES | YES | YES | YES | YES | YES | + +### Gap Feasibility Matrix (Walk, 12t Momentum) + +| Gap | dy=+1.0 | dy=+0.5 | dy=0 | dy=-1 | dy=-2 | dy=-3 | dy=-5 | +|---|---|---|---|---|---|---|---| +| 0 | YES | YES | YES | YES | YES | YES | YES | +| 1 | YES | YES | YES | YES | YES | YES | YES | +| 2 | YES | YES | YES | YES | YES | YES | YES | +| 3 | YES | YES | YES | YES | YES | YES | YES | +| 4 | YES | YES | YES | YES | YES | YES | YES | +| 5 | no | no | YES | YES | YES | YES | YES | + +### Gap Feasibility Matrix (Standing Sprint Jump, 0t Momentum) + +| Gap | dy=+1.0 | dy=+0.5 | dy=0 | dy=-1 | dy=-2 | dy=-3 | dy=-5 | +|---|---|---|---|---|---|---|---| +| 0 | YES | YES | YES | YES | YES | YES | YES | +| 1 | YES | YES | YES | YES | YES | YES | YES | +| 2 | no | YES | YES | YES | YES | YES | YES | +| 3 | no | no | no | no | no | no | no | + +### Neo Jump Analysis (Flat, 12t Momentum) + +For a wall of N blocks, the player must travel at least N + 0.6m forward +to clear the wall end (accounting for 0.6m player bounding box width). + +| Wall Length | Sprint Reach | Needed | Margin | Feasible | +|---|---|---|---|---| +| 1b | 7.728m | 1.6m | +6.128 | YES | +| 2b | 7.728m | 2.6m | +5.128 | YES | +| 3b | 7.728m | 3.6m | +4.128 | YES | +| 4b | 7.728m | 4.6m | +3.128 | YES | + +Note: the neo analysis uses simplified straight-line reach. In practice, +the player must also perform a lateral (sideways) movement to round the +wall corner, which reduces effective forward distance slightly. The large +margins suggest all 1-4 block neos are comfortably achievable. + +### Ceiling-Constrained Jumps (Sprint, 12t Momentum) + +Lower ceilings reduce jump height and therefore reduce horizontal distance: + +| Ceiling Height | Landing X | Delta vs Open | +|---|---|---| +| 4.0b (no effect) | 7.728m | +0.000 | +| 3.0b | 7.415m | -0.313 | +| 2.5b | 5.689m | -2.039 | +| 2.0bc (headhitter) | 4.482m | -3.246 | +| 1.8125bc (trapdoor hh) | 4.042m | -3.687 | + +### Sprint Jump Trajectory (12 tick momentum, flat landing) + +| Tick | Phase | X | Y | VX | VY | +|---|---|---|---|---|---| +| 0-12 | Momentum (ground) | 0 -> 3.09 | 0 | 0 -> 0.156 | 0 | +| 13 | Jump tick | 3.58 | 0.42 | 0.443 | 0.333 | +| 14 | Rising | 4.04 | 0.75 | 0.421 | 0.248 | +| 15 | Rising | 4.48 | 1.00 | 0.401 | 0.165 | +| 16 | Rising | 4.90 | 1.17 | 0.382 | 0.083 | +| 17 | Apex | 5.30 | 1.25 | 0.366 | 0.003 | +| 18 | Falling | 5.69 | 1.25 | 0.351 | -0.075 | +| 19-23 | Falling | 5.69 -> 7.42 | 1.25 -> 0.12 | 0.351 -> 0.293 | accelerating | +| 24 | Landing | 7.73 | 0.00 | 0.171 | 0 | + +Total airborne time: 11 ticks (tick 13-24). + +### Implications for Pathfinding + +Based on these results, the initial pathfinding scope should include: + +1. **Standard jumps**: sprint jump can clear up to 5 block gaps (flat) + and 4-5 block gaps with +1.0 height, with full momentum. + +2. **Standing sprint jumps**: only reliable for up to 1 block gap with + +1 height, or 2 block gap flat. This is relevant for confined spaces + where a long run-up is unavailable. + +3. **Neo jumps (1-2 block walls)**: comfortable margin with sprint. + The pathfinder should include these as standard movement options. + +4. **Ascending jumps (+1 block)**: always feasible with sprint for gaps + up to 5 blocks. The key constraint is the 1.252 block jump height + limit, meaning +1.0 is fine but +1.25+ is extremely marginal. + +5. **Ceiling constraint**: a 2bc (headhitter) ceiling cuts reach roughly + in half. The pathfinder should detect ceiling height and adjust the + maximum jump gap accordingly. + +## Reliability-first rule + +Every movement proposal generated by the MCC pathfinder must be grounded in reality: if a move is accepted, it must be one the bot can execute in vanilla 1.21.11 physics. That means the final support footprint is the ultimate arbiter: if the planner can get the player onto a solid block (even if they momentarily hover over air during the transition), the move is considered valid. Conversely, any shape that would finish without block contact, rely on unsupported parkour tricks, or require a start-up/run-up that the current layout cannot provide must be rejected rather than downgraded to a risky heuristic. + +The new regression harness in `tools/test-pathing-template-regressions.sh` codifies this rule by automating: + +1. Flat-stopping scenarios that ensure the arrival block is within the planner’s tolerance. +2. Parkour + L-turn footprints to watch for actual support at the destination. +3. Side-wall jump acceptance conditioned on an executable landing. +4. A 3×1 no-run-up rejection to prevent non-executable plans from sneaking through. +5. Mixed ascend/descend/climb smoke cases so that both vertical transitions and ladder climbs respect the reliable support requirement. + +## Deterministic pathing contract + +Accepted sterile routes must satisfy all of the following: + +- planner result is `Success` +- planner result is not `Partial` +- navigation completes with `0` replans +- total route ticks stay within the checked-in route budget +- every executed segment stays within its checked-in tick budget + +Rejected sterile routes must fail during planning and must never start navigation. + +The checked-in contract files live here: + +- `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` + +For unit tests, `PathingContractAssert` reports planner mismatches, replans, route totals, and per-segment tick overruns directly in the xUnit failure. For live runs, `tools/pathing_contract_report.py` reads the `[PathMetric]` lines emitted by MCC and prints the same route-level and segment-level view, so the offline and live harnesses fail for the same reasons. + +## Theory-Aligned Regression Workflow + +The first-wave theory authority now comes from `tools/sim_jump_reach.py`, which writes: + +- `tools/pathing_data/theory-matrix.json` +- `tools/pathing_data/theory-matrix.csv` +- `tools/pathing_data/theory-matrix.md` +- `tools/pathing_data/canonical-live-cases.json` + +Regenerate them with: + +```bash +python3 tools/sim_jump_reach.py --write-artifacts tools/pathing_data +``` + +The theory-aligned live suites read the canonical manifest instead of embedding their own pass and reject expectations: + +- `tools/test-parkour.sh` +- `tools/test-pathing-theory-neo-ceiling.sh` + +That split matters. The theory matrix stays broad and machine-readable. The canonical manifest stays small enough to run live, and the shell suites only need to care about case setup, execution, and recording the outcome. + +## Deterministic live route contract + +For the short-route and long-route `1.21.11-Vanilla` live harnesses, accepted routes must complete with all of the following: + +- `A* result: Success` +- `0 replan` +- `0` template segment failures +- final position inside the intended goal support block +- `PathMgr` reporting `Navigation complete!` + +For rejection scenarios, the requirement is stricter: + +- `A* result: Failed` or `No path found` +- no navigation start +- no executor-driven `replan` + +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`. + +## 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. + +Borrow: + +- landing-aware completion, where movement logic keeps controlling after touchdown instead of failing immediately +- next-movement-aware descend and ascend handoff behavior +- conservative parkour admissibility, especially around run-up, overshoot, and blocked landing shapes +- executor timeout and movement-stuck heuristics as diagnostic input, not as acceptance criteria + +Do not borrow: + +- `GoalBlock` occupancy semantics as a substitute for deterministic execution quality +- executor repath tolerance as proof that a movement is reliable +- any behavior that lets accepted deterministic harness routes succeed only by falling back to `replan` + +For this work, Baritone is a movement-control reference, not a correctness oracle. MCC's accepted live routes must still finish with `0 replan` in the deterministic harness. + +Keeping the rule explicit here reminds future contributors that the planner should never promise a move that physically cannot finish with block contact. + +## Regression Harness Workflow + +The scripts in `tools/` now match the `mcc-dev-workflow` defaults: they call +`source tools/mcc-env.sh`, rely on a shared `mc-*` server running `1.21.11-Vanilla`, +and launch MCC through `mcc-build`, `mcc-debug`, and `mcc-cmd` wrappers. The +harnesses reuse the existing server session instead of stopping and restarting +it, which keeps shared test infrastructure stable and honors the instruction to +keep `mc-*` servers running unless another version or explicit reset is required. +When editing or extending the harness, preserve the `mcc-*` invocation pattern +and the existing log/tail helpers so the scripts stay compatible with the updated +workflow. + +## References + +- [Minecraft Parkour Wiki: Blip](https://www.mcpk.wiki/wiki/Blip) +- [Minecraft Parkour Wiki: Stepping](https://www.mcpk.wiki/wiki/Stepping) +- [Minecraft Parkour Wiki: Jump Cancel](https://www.mcpk.wiki/wiki/Jump_Cancel) +- [Minecraft Parkour Wiki: Parkour Nomenclature](https://www.mcpk.wiki/wiki/Parkour_Nomenclature) +- [Minecraft Parkour Wiki: Collisions](https://www.mcpk.wiki/wiki/Collisions) diff --git a/docs/superpowers/plans/2026-04-12-mcc-shared-server-isolated-sessions.md b/docs/superpowers/plans/2026-04-12-mcc-shared-server-isolated-sessions.md index 975787b939..330cd918a1 100644 --- a/docs/superpowers/plans/2026-04-12-mcc-shared-server-isolated-sessions.md +++ b/docs/superpowers/plans/2026-04-12-mcc-shared-server-isolated-sessions.md @@ -320,7 +320,7 @@ Run: ```bash source tools/mcc-env.sh -mcc-debug -v 1.21.11 --file-input --no-build +mcc-debug -v 1.21.11-Vanilla --file-input --no-build ls -la /tmp/mcc-debug tmux list-sessions | grep '^mcc-debug:' ``` @@ -432,8 +432,8 @@ Run: ```bash source tools/mcc-env.sh -mcc-debug -v 1.21.11 --session smoke-a --username SmokeA --file-input --no-build -mcc-debug -v 1.21.11 --session smoke-b --username SmokeB --file-input --no-build +mcc-debug -v 1.21.11-Vanilla --session smoke-a --username SmokeA --file-input --no-build +mcc-debug -v 1.21.11-Vanilla --session smoke-b --username SmokeB --file-input --no-build test -f "$(_mcc_session_log_file smoke-a)" test -f "$(_mcc_session_log_file smoke-b)" test -f "$(_mcc_session_meta_file smoke-a)" @@ -602,7 +602,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" source "$REPO_ROOT/tools/mcc-env.sh" source "$SCRIPT_DIR/common.sh" -VERSION="${1:-1.21.11}" +VERSION="${1:-1.21.11-Vanilla}" SESSION_A="parallel-a" SESSION_B="parallel-b" USER_A="ParallelA" @@ -631,7 +631,7 @@ mc-log "$VERSION" 50 | grep -Fq "$USER_B joined the game" - [ ] **Step 2: 运行脚本,确认它先因为新参数或旧的共享路径逻辑而失败** -Run: `bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11` +Run: `bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11-Vanilla` Expected: FAIL,错误类似 `Unknown option: --session`、固定 `mcc_input.txt` 被共用,或者只有一个客户端会话存活 @@ -680,9 +680,9 @@ mkdir -p "$(_mcc_session_root "$SESSION")" Run: ```bash -bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11 -bash tools/run-creative-e2e.sh 1.21.11 1.21.11 modern -bash .skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh 1.21.11 +bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11-Vanilla +bash tools/run-creative-e2e.sh 1.21.11-Vanilla 1.21.11 modern +bash .skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh 1.21.11-Vanilla ``` Expected: 三个脚本都 PASS;并行 smoke test 中一个 session 被 kill 后,另一个 session 和共享服务器继续存活 @@ -722,13 +722,13 @@ git commit -m "test: cover shared server with isolated MCC sessions" # worktree A cd ~/Minecraft/Minecraft-Console-Client source tools/mcc-env.sh -mc-start 1.21.11 -mcc-debug -v 1.21.11 --file-input +mc-start 1.21.11-Vanilla +mcc-debug -v 1.21.11-Vanilla --file-input # worktree B cd ~/Minecraft/Minecraft-Console-Client-foo source tools/mcc-env.sh -mcc-debug -v 1.21.11 --file-input +mcc-debug -v 1.21.11-Vanilla --file-input # Each worktree gets: # - its own session @@ -761,7 +761,7 @@ Run: bash tools/test-mcc-env.sh source tools/mcc-env.sh && unset MCC_BUILD_MODE && mcc-build source tools/mcc-env.sh && export MCC_BUILD_MODE=tmpfs && mcc-build -bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11 +bash .skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh 1.21.11-Vanilla ``` Expected: diff --git a/docs/superpowers/plans/2026-04-12-parkour-admissibility-plan.md b/docs/superpowers/plans/2026-04-12-parkour-admissibility-plan.md new file mode 100644 index 0000000000..5548736ece --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-parkour-admissibility-plan.md @@ -0,0 +1,210 @@ +# Parkour Admissibility Hardening Implementation Plan + +I'm using the writing-plans skill to create the 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:** Harden MoveParkour by factoring conservative run-up, diagonal-shoulder, and landing-overshoot checks into a helper, tightening MoveParkour’s acceptance, and covering the regression cases with deterministic tests. + +**Architecture:** Inject a new `ParkourFeasibility` helper that owns the admissibility rules so MoveParkour can simply call it before running the existing flight-path and destination checks; keep the helper self-contained so future moves can reuse it without touching the MoveParkour flow. + +**Tech Stack:** .NET 10 / C# 14, xUnit, dotnet CLI + +--- + +### Task 1: Create ParkourFeasibility helper + +**Files:** +- Create: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` + +- [ ] **Step 1: Implement the helper class with the three checks** + +```csharp +namespace MinecraftClient.Pathing.Moves; + +internal static class ParkourFeasibility +{ + public static bool HasRunUp( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset, + int yDelta) + { + double horiz = Math.Sqrt(xOffset * xOffset + zOffset * zOffset); + double threshold = yDelta > 0 ? 2.5 : 3.5; + if (horiz < threshold) + return true; + + int backX = x - Math.Sign(xOffset); + int backZ = z - Math.Sign(zOffset); + if (!ctx.CanWalkOn(backX, y - 1, backZ)) + return false; + return IsColumnPassable(ctx, backX, y, backZ); + } + + public static bool HasDiagonalShoulderClearance( + CalculationContext ctx, + int x, + int y, + int z, + int xOffset, + int zOffset) + { + if (xOffset == 0 || zOffset == 0) + return true; + + return IsColumnPassable(ctx, x + Math.Sign(xOffset), y, z) + && IsColumnPassable(ctx, x, y, z + Math.Sign(zOffset)); + } + + public static bool HasLandingOvershootClearance( + CalculationContext ctx, + int destX, + int destY, + int destZ, + int xSign, + int zSign) + { + return IsColumnPassable(ctx, destX + xSign, destY, destZ + zSign); + } + + private static bool IsColumnPassable(CalculationContext ctx, int x, int y, int z) + { + if (!ctx.CanWalkThrough(x, y, z) || + !ctx.CanWalkThrough(x, y + 1, z) || + !ctx.CanWalkThrough(x, y + 2, z)) + return false; + + return true; + } +} +``` + +- [ ] **Step 2: Verify the helper compiles by building the solution** + +Run: `dotnet build MinecraftClient.sln -c Release` +Expected: `Build succeeded.` + +### Task 2: Update MoveParkour to rely on the helper + +**Files:** +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` + +- [ ] **Step 1: Replace the existing run-up block with the helper** + +```csharp +if (!ParkourFeasibility.HasRunUp(ctx, x, y, z, XOffset, ZOffset, _yDelta)) +{ + result.SetImpossible(); + return; +} +``` + +- [ ] **Step 2: Replace the diagonal shoulder + overshoot handling with helper calls** + +```csharp +if (!ParkourFeasibility.HasDiagonalShoulderClearance(ctx, x, y, z, XOffset, ZOffset)) +{ + result.SetImpossible(); + return; +} + +if (!ParkourFeasibility.HasLandingOvershootClearance(ctx, destX, destY, destZ, xSign, zSign)) +{ + result.SetImpossible(); + return; +} +``` + +### Task 3: Add MoveParkour unit tests + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs` + +- [ ] **Step 1: Add tests for the three scenarios** + +```csharp +public sealed class MoveParkourTests +{ + private const int FloorY = 79; + + private static CalculationContext BuildContext(World world) + => new(world, allowParkour: true, allowParkourAscend: true); + + [Fact] + public void RejectsLongJumpWithoutRunUp() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(-1, FloorY, 0), Block.Air); // remove run-up + var ctx = BuildContext(world); + var move = new MoveParkour(3, 0); + var result = default(MoveResult); + + move.Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void AllowsShortJumpWithClearTakeoff() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + var ctx = BuildContext(world); + var result = default(MoveResult); + new MoveParkour(2, 0).Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(2, result.DestX); + } + + [Fact] + public void RejectsDiagonalWhenShoulderBlocked() + { + var world = FlatWorldTestBuilder.CreateStoneFloor(FloorY); + world.SetBlock(new Location(1, FloorY + 1, 0), new Block(1)); + var ctx = BuildContext(world); + var result = default(MoveResult); + new MoveParkour(1, 1).Calculate(ctx, 0, FloorY + 1, 0, ref result); + + Assert.True(result.IsImpossible); + } +} +``` + +- [ ] **Step 2: Run the new tests to confirm they fail until implementation completes** + +Run: `dotnet test MinecraftClient.Tests --filter MoveParkourTests` +Expected: FAIL (the tests fail until Tasks 1–2 are finished) + +### Task 4: Validation + +**Files:** No new files; just validation commands. + +- [ ] **Step 1: Run the targeted test suite after implementation changes** + +Run: `dotnet test MinecraftClient.Tests --filter MoveParkourTests` +Expected: PASS all tests in the class. + +### Task 5: Commit (optional after verification) + +**Files:** +- Modify: the ones mentioned above (`ParkourFeasibility.cs`, `MoveParkour.cs`, `MoveParkourTests.cs`, plan/spec files) + +- [ ] **Step 1: Stage the affected files** + +```bash +git add MinecraftClient/Pathing/Moves/ParkourFeasibility.cs \ + MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs \ + MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs \ + docs/superpowers/specs/2026-04-12-parkour-admissibility-design.md \ + docs/superpowers/plans/2026-04-12-parkour-admissibility-plan.md +``` + +- [ ] **Step 2: Commit with a descriptive message** + +```bash +git commit -m "feat: harden parkour admissibility" +``` diff --git a/docs/superpowers/plans/2026-04-12-pathing-live-regression-convergence.md b/docs/superpowers/plans/2026-04-12-pathing-live-regression-convergence.md new file mode 100644 index 0000000000..2d7bb79794 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-pathing-live-regression-convergence.md @@ -0,0 +1,425 @@ +# Pathing Live Regression Convergence 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:** Make every remaining movement template that currently passes deterministic simulation but fails on the real 1.21.11 server converge to the same reliable outcome in both environments. + +**Architecture:** Keep the existing move catalog and support-footprint completion rules, but close the sim/live gaps at the transition layer. The main tactic is to encode each live-only failure as a deterministic regression first, then fix the responsible handoff logic so braking, heading lock, and completion semantics stay consistent across `SprintJumpTemplate`, grounded recovery, and the local server harness. + +**Tech Stack:** C# 14 / .NET 10, MCC `PlayerPhysics`, xUnit, bash harnesses under `tools/`, local offline 1.21.11 server via `tools/mcc-env.sh`. + +--- + +## Execution Context + +The user explicitly asked to stay in the current workspace, not a worktree. Do not revert unrelated dirty files. The precision bar is not “exactly at center”; the bar is “footprint fully supported, no unsafe drift past the intended support edge, and no segment failure hidden by replanning”. + +## Scope + +In scope: + +- `LandingRecovery` regressions caused by the braking feature +- short parkour into turn / wall-adjacent follow-up moves that still fail live +- template and planner mismatches where deterministic tests are missing the real-server failure mode +- regression harness updates that fail on any segment failure instead of accepting a later replan + +Out of scope for this pass: + +- a global SafeWalk / always-sneak system +- new movement types +- large A* or cost-model rewrites unrelated to live regressions + +## File Structure + +### New files + +- `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + Deterministic reproductions of the currently known live-only failures, seeded from real harness geometry and residual landing states. + +### Modified files + +- `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + Teach the planner that `LandingRecovery` may still require a real ground brake before the next heading change. +- `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + Keep landing recovery aligned with the planner and avoid drifting out of the landing support while preparing the next move. +- `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` + Reuse the corrected planner behavior for grounded completion and braking. +- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + Preserve high-level parkour coverage after the targeted regression tests land. +- `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + Add planner-level assertions for `LandingRecovery` into turns and other non-straight follow-ups. +- `tools/test-pathing-template-regressions.sh` + Extend the live harness cases as each new real-only failure is discovered and fixed. + +--- + +### Task 1: Encode The Live `LandingRecovery -> Turn` Failure + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` +- Test: `MinecraftClient.Tests/MinecraftClient.Tests.csproj` + +- [ ] **Step 1: Write the failing planner and live-geometry regression tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs +[Fact] +public void Plan_BackBrakes_ForLandingRecovery_WhenNextSegmentTurns() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.118, 0.000, onGround: true); + 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 + }; + var next = new PathSegment + { + Start = new Location(122.5, 80, 110.5), + End = new Location(122.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan( + current, + next, + new Location(122.56, 80.0, 110.68), + physics, + world); + + Assert.False(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.True(decision.HoldBack); +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class LivePathingRegressionTests +{ + [Fact] + public void LandingRecoveryIntoTurn_HoldsInsideLandingBlock_FromLiveLikeState() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 118, 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); + FlatWorldTestBuilder.SetSolid(world, 120, 80, 111); + FlatWorldTestBuilder.SetSolid(world, 120, 81, 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 + }; + var next = new PathSegment + { + Start = new Location(122.5, 80, 110.5), + End = new Location(122.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(122.56, 80.0, 110.68), + DeltaMovement = new Vec3d(0.118, 0.0, 0.018), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f, + Pitch = 0f + }; + + var input = new MovementInput(); + GroundedSegmentController.Apply(current, next, new Location(122.56, 80.0, 110.68), physics, input, world); + + Assert.True(input.Back); + physics.ApplyInput(input); + physics.Tick(world); + + Location settled = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(settled, current.End)); + } +} +``` + +- [ ] **Step 2: Run the targeted tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "Plan_BackBrakes_ForLandingRecovery_WhenNextSegmentTurns|LandingRecoveryIntoTurn_HoldsInsideLandingBlock_FromLiveLikeState" -v minimal +``` + +Expected: FAIL because `LandingRecovery` currently falls through to the generic coast branch and does not hold `Back`. + +- [ ] **Step 3: Commit the failing regression capture** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs \ + MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +git commit -m "test: capture live landing recovery turn regression" +``` + +--- + +### Task 2: Teach `LandingRecovery` To Brake For Non-Straight Follow-Ups + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + +- [ ] **Step 1: Implement the minimal planner change** + +```csharp +// MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs +public static TransitionBrakingDecision Plan(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) +{ + if (current.ExitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump) + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); + + double remaining = RemainingDistanceAlongSegment(current, pos); + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ)); + double coastStopDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: false); + double hardBrakeDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: true); + + bool landingNeedsTurnBrake = current.ExitTransition == PathTransitionType.LandingRecovery + && next is not null + && (current.HeadingX != next.HeadingX || current.HeadingZ != next.HeadingZ); + + if (current.ExitTransition == PathTransitionType.FinalStop) + { + if (remaining < 0.0) + return TransitionBrakingDecision.Brake; + + if (forwardSpeed > GroundSpeedThreshold && remaining <= hardBrakeDistance + FinalBrakeLead) + return TransitionBrakingDecision.Brake; + + if (forwardSpeed <= GroundSpeedThreshold && remaining > 0.0) + return TransitionBrakingDecision.CarryMomentum(preserveSprint: false); + } + + if ((current.ExitTransition == PathTransitionType.Turn || landingNeedsTurnBrake) + && remaining <= hardBrakeDistance + TurnBrakeLead) + { + return TransitionBrakingDecision.Brake; + } + + if (remaining <= coastStopDistance + FinalStopLead) + return TransitionBrakingDecision.Coast; + + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); +} +``` + +- [ ] **Step 2: Keep grounded braking aligned with the planner** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs +internal static void Apply(PathSegment segment, PathSegment? nextSegment, Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(segment, nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + + if (decision.HoldBack) + TemplateHelper.FaceSegmentHeading(physics, segment); +} +``` + +- [ ] **Step 3: Run the targeted tests to verify they pass** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "Plan_BackBrakes_ForLandingRecovery_WhenNextSegmentTurns|LandingRecoveryIntoTurn_HoldsInsideLandingBlock_FromLiveLikeState" -v minimal +``` + +Expected: PASS with `2 Passed`. + +- [ ] **Step 4: Commit the planner fix** + +```bash +git add MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs \ + MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs \ + MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs \ + MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +git commit -m "fix: brake landing recovery before turns" +``` + +--- + +### Task 3: Keep `SprintJumpTemplate` Aligned With The Ground Brake + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Test: `MinecraftClient.Tests/MinecraftClient.Tests.csproj` + +- [ ] **Step 1: Add a template-level regression for the exact L-turn geometry** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs +[Fact] +public void SprintJumpTemplate_TwoBlockGap_LandingRecovery_IntoTurn_CompletesWithoutLeavingLandingBlock() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 118, 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); + FlatWorldTestBuilder.SetSolid(world, 120, 80, 111); + FlatWorldTestBuilder.SetSolid(world, 120, 81, 111); + + var segment = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(122.5, 80, 110.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.LandingRecovery + }; + var next = new PathSegment + { + Start = new Location(122.5, 80, 110.5), + End = new Location(122.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); +} +``` + +- [ ] **Step 2: Make landing recovery respect the same brake/heading contract as grounded segments** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +case Phase.Landing: + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + if (decision.HoldBack) + TemplateHelper.FaceSegmentHeading(physics, _segment); + + if (_segment.ExitTransition == PathTransitionType.ContinueStraight + && horizDistSq < horizToleranceSq && Math.Abs(dy) < vertTolerance) + return TemplateState.Complete; + + if (_segment.ExitTransition != PathTransitionType.ContinueStraight + && physics.OnGround + && TemplateHelper.IsSettledOnTargetBlock(pos, ExpectedEnd, physics)) + { + return TemplateState.Complete; + } + break; +``` + +- [ ] **Step 3: Run the parkour template test slice** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "SprintJumpTemplate_TwoBlockGap_LandingRecovery_IntoTurn_CompletesWithoutLeavingLandingBlock|SprintJumpTemplate_TwoBlockGap_LandingRecovery_CompletesInsideLandingBlock|SprintJumpTemplate_TwoBlockGap_FinalStop_Completes|SprintJumpTemplate_ThreeBlockGap_FinalStop_Completes" -v minimal +``` + +Expected: PASS with `4 Passed`. + +- [ ] **Step 4: Commit the template alignment** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs +git commit -m "fix: align sprint jump landing recovery with turn braking" +``` + +--- + +### Task 4: Sweep Remaining Sim/Live Gaps With The Real Harness + +**Files:** +- Modify: `tools/test-pathing-template-regressions.sh` +- Modify: `docs/superpowers/plans/2026-04-12-pathing-live-regression-convergence.md` +- Test: `MinecraftClient.Tests/MinecraftClient.Tests.csproj` + +- [ ] **Step 1: Extend the live harness with every newly discovered real-only failure** + +```bash +# tools/test-pathing-template-regressions.sh +# Add one function per new repro: +# - run_wall_adjacent_landing_recovery +# - run_around_wall_jump_followup +# - run_short_descend_into_turn +# Each function must: +# 1. build the exact world with mc-rcon +# 2. teleport MCCBot +# 3. send the pathfind command +# 4. fail immediately on any "[PathExec] Segment .* FAILED" +# 5. assert the final location or assert explicit planner rejection +``` + +- [ ] **Step 2: Run the full deterministic suite** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj -v minimal +``` + +Expected: PASS with the full suite green. + +- [ ] **Step 3: Run the release build** + +Run: + +```bash +dotnet build MinecraftClient.sln -c Release +``` + +Expected: `Build succeeded.` + +- [ ] **Step 4: Run the real 1.21.11 harness** + +Run: + +```bash +bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected: + +```text +== Flat final stop == +== Parkour into L-turn == +== Rejected 2x1 side-wall jump == +== Rejected 3x1 no-run-up gap == +All pathing template regression checks passed for 1.21.11. +``` + +- [ ] **Step 5: Commit the harness convergence** + +```bash +git add tools/test-pathing-template-regressions.sh \ + docs/superpowers/plans/2026-04-12-pathing-live-regression-convergence.md +git commit -m "test: extend live pathing regression coverage" +``` diff --git a/docs/superpowers/plans/2026-04-12-pathing-lookahead-entry-contracts.md b/docs/superpowers/plans/2026-04-12-pathing-lookahead-entry-contracts.md new file mode 100644 index 0000000000..3940e4f925 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-pathing-lookahead-entry-contracts.md @@ -0,0 +1,1064 @@ +# Pathing Lookahead Entry Contracts 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:** Upgrade MCC path execution from coarse next-segment braking to explicit entry contracts and short-horizon input selection so turns, jump takeoffs, landings, and final stops converge reliably on 1.21.11 without overshoot. + +**Architecture:** Keep A* node expansion and move admissibility mostly unchanged. Extend `PathSegment` with quantitative transition hints derived from the next one or two segments, teach the braking planner to score candidate inputs against those hints with cloned `PlayerPhysics` simulations, and update grounded and airborne templates to hand off only when the next action's entry contract is actually satisfied. + +**Tech Stack:** C# 14 / .NET 10, MCC `PlayerPhysics`, xUnit deterministic simulation tests in `MinecraftClient.Tests`, local MCC harness via `tools/mcc-env.sh`, `mcc-build`, `mcc-debug`, `mcc-cmd`, and a shared local 1.21.11 server. + +--- + +## Execution Context + +This plan starts from the current repository state, not the older "transition braking from scratch" plan. The test project, `PathTransitionType`, `TransitionBrakingPlanner`, convergence tests, and `tools/test-transition-braking.sh` already exist. + +This plan is the follow-on slice for the later requirement from the broken conversation: + +- planner and executor should anticipate whether the next action continues momentum, requires a turn, or requires a jump takeoff +- braking may begin on the previous segment +- airborne forward release is valid and should be planned, not guessed +- "precision" means satisfying the next action's entry conditions, not snapping to exact block center + +## Scope + +In scope: + +- add explicit quantitative transition hints to `PathSegment` +- let the execution layer see beyond a coarse `Turn` / `PrepareJump` enum +- replace threshold-only braking decisions with short-horizon candidate simulation +- improve walk, descend, and sprint-jump handoff behavior +- update the local 1.21.11 regression harness to the current `mcc-dev-workflow` + +Out of scope: + +- changing A* heuristics or move costs unrelated to transition control +- adding a general-purpose "teleport to center" or velocity-zeroing cheat +- expanding the move catalog beyond current traverse / ascend / descend / parkour behavior + +## File Structure + +### New files + +- `MinecraftClient/Pathing/Execution/PathTransitionHints.cs` + Immutable quantitative exit contract for one segment: desired heading, minimum and maximum exit speed, stability requirements, and short planning horizon. +- `MinecraftClient/Pathing/Execution/TransitionInputProfile.cs` + Named candidate inputs for the planner to score, such as carry, coast, brake, airborne hold, and airborne release. +- `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs` + Clones `PlayerPhysics`, simulates candidate inputs for a small horizon, and scores them against `PathTransitionHints`. +- `MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs` + Verifies the segment builder derives correct hints for straight carry, turn entry, final stop, and prepare-jump cases. +- `MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs` + Verifies candidate scoring chooses carry, coast, brake, or airborne release in representative scenarios. + +### Modified files + +- `MinecraftClient/Pathing/Execution/PathSegment.cs` + Carry the quantitative exit hints alongside `ExitTransition`. +- `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` + Compute hints from `current`, `next`, and `nextNext` segments. +- `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + Use the lookahead evaluator instead of only distance thresholds. +- `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + Add heading-readiness helpers and any small shared utilities needed by the evaluator. +- `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` + Complete only when the current segment satisfies its exit hints. +- `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` + Stop treating "near segment end" as sufficient when the next action needs a slow turn or jump-ready takeoff. +- `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` + Decide whether to carry, coast, or release before stepping off a ledge when the landing must turn or stop. +- `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + Replace heuristic airborne release with contract-aware candidate selection. +- `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` + Extend coarse transition tests to assert the new hint values. +- `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + Update existing planner tests to assert the new evaluator-backed choices. +- `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` + Add handoff scenarios that fail if the current segment arrives too fast or too slow for the next one. +- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + Add jump-entry and landing-entry scenarios with explicit residual speed assertions. +- `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + Keep a deterministic regression for the "parkour into turn" case that started this investigation. +- `tools/test-transition-braking.sh` + Move the harness to `mcc-build`, `mcc-debug`, and `mcc-cmd`, and extend it with lookahead-sensitive scenarios. +- `docs/guide/pathfinding-research.md` + Document the new rule: the executor aims for a valid next-action entry state, not a geometric center point. + +--- + +### Task 1: Add Quantitative Transition Hints to `PathSegment` + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/PathTransitionHints.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegment.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` + +- [ ] **Step 1: Write the failing hint-derivation tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs +using System.Collections.Generic; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathTransitionHintsTests +{ + [Fact] + public void FromPath_AssignsTurnHints_WhenNextSegmentChangesHeading() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (1, 80, 1, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition); + Assert.True(hints.RequireStableFooting); + Assert.True(hints.RequireGrounded); + Assert.Equal(0, hints.DesiredHeadingX); + Assert.Equal(1, hints.DesiredHeadingZ); + Assert.InRange(hints.MaxExitSpeed, 0.0, 0.05); + } + + [Fact] + public void FromPath_AssignsJumpReadyHints_WhenNextSegmentIsParkour() + { + var nodes = BuildNodes( + (120, 80, 110, MoveType.Traverse), + (121, 80, 110, MoveType.Traverse), + (123, 80, 110, MoveType.Parkour)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition); + Assert.True(hints.RequireJumpReady); + Assert.False(hints.RequireStableFooting); + Assert.Equal(1, hints.DesiredHeadingX); + Assert.Equal(0, hints.DesiredHeadingZ); + Assert.True(hints.MinExitSpeed >= 0.10, $"MinExitSpeed={hints.MinExitSpeed}"); + } + + [Fact] + public void FromPath_AssignsPreciseStopHints_WhenSegmentIsFinalStop() + { + var nodes = BuildNodes( + (10, 80, 10, MoveType.Traverse), + (11, 80, 10, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + PathTransitionHints hints = segments[0].ExitHints; + + Assert.Equal(PathTransitionType.FinalStop, segments[0].ExitTransition); + Assert.True(hints.RequireStableFooting); + Assert.True(hints.RequireGrounded); + Assert.InRange(hints.MaxExitSpeed, 0.0, 0.02); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + + return result; + } +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathTransitionHintsTests|FullyQualifiedName~PathSegmentBuilderTests" -v minimal +``` + +Expected: FAIL with compile errors because `PathTransitionHints` and `PathSegment.ExitHints` do not exist yet. + +- [ ] **Step 3: Implement the hint type and derive it in the segment builder** + +```csharp +// MinecraftClient/Pathing/Execution/PathTransitionHints.cs +namespace MinecraftClient.Pathing.Execution +{ + public sealed record PathTransitionHints( + int DesiredHeadingX, + int DesiredHeadingZ, + double MinExitSpeed, + double MaxExitSpeed, + bool RequireStableFooting, + bool RequireGrounded, + bool RequireJumpReady, + bool AllowAirBrake, + int HorizonTicks) + { + public static PathTransitionHints Default { get; } = new( + DesiredHeadingX: 0, + DesiredHeadingZ: 0, + MinExitSpeed: 0.0, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: false, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 8); + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegment.cs +public sealed class PathSegment +{ + public required Location Start { get; init; } + public required Location End { get; init; } + public required MoveType MoveType { get; init; } + public PathTransitionType ExitTransition { get; init; } = PathTransitionType.FinalStop; + public PathTransitionHints ExitHints { get; init; } = PathTransitionHints.Default; + public bool PreserveSprint { get; init; } + + public int HeadingX => Math.Sign(End.X - Start.X); + public int HeadingZ => Math.Sign(End.Z - Start.Z); +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs +public static List FromPath(IReadOnlyList nodes) +{ + var segments = new List(Math.Max(0, nodes.Count - 1)); + for (int i = 1; i < nodes.Count; i++) + { + PathSegment? next = i + 1 < nodes.Count ? CreatePreview(nodes[i], nodes[i + 1]) : null; + PathSegment? nextNext = i + 2 < nodes.Count ? CreatePreview(nodes[i + 1], nodes[i + 2]) : null; + PathSegment current = CreatePreview(nodes[i - 1], nodes[i]); + + PathTransitionType exitTransition = Classify(current, next); + PathTransitionHints exitHints = BuildHints(current, next, nextNext, exitTransition); + + segments.Add(new PathSegment + { + Start = current.Start, + End = current.End, + MoveType = current.MoveType, + ExitTransition = exitTransition, + ExitHints = exitHints, + PreserveSprint = exitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump + }); + } + + return segments; +} + +private static PathTransitionHints BuildHints(PathSegment current, PathSegment? next, PathSegment? nextNext, PathTransitionType exitTransition) +{ + if (next is null) + { + return new PathTransitionHints( + current.HeadingX, + current.HeadingZ, + MinExitSpeed: 0.0, + MaxExitSpeed: 0.01, + RequireStableFooting: true, + RequireGrounded: true, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 12); + } + + if (next.MoveType is MoveType.Parkour or MoveType.Ascend) + { + double minExitSpeed = next.MoveType == MoveType.Parkour ? 0.12 : 0.10; + return new PathTransitionHints( + next.HeadingX, + next.HeadingZ, + MinExitSpeed: minExitSpeed, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: true, + RequireJumpReady: true, + AllowAirBrake: false, + HorizonTicks: 10); + } + + bool turning = current.HeadingX != next.HeadingX || current.HeadingZ != next.HeadingZ; + bool nextImmediatelyJumps = nextNext is not null && nextNext.MoveType is MoveType.Parkour or MoveType.Ascend; + + if (turning) + { + return new PathTransitionHints( + next.HeadingX, + next.HeadingZ, + MinExitSpeed: nextImmediatelyJumps ? 0.08 : 0.0, + MaxExitSpeed: 0.035, + RequireStableFooting: true, + RequireGrounded: true, + RequireJumpReady: nextImmediatelyJumps, + AllowAirBrake: true, + HorizonTicks: 12); + } + + return new PathTransitionHints( + next.HeadingX, + next.HeadingZ, + MinExitSpeed: 0.08, + MaxExitSpeed: double.PositiveInfinity, + RequireStableFooting: false, + RequireGrounded: false, + RequireJumpReady: false, + AllowAirBrake: false, + HorizonTicks: 8); +} +``` + +- [ ] **Step 4: Re-run the hint tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathTransitionHintsTests|FullyQualifiedName~PathSegmentBuilderTests" -v minimal +``` + +Expected: PASS with all path-segment hint tests green. + +- [ ] **Step 5: Commit the segment metadata slice** + +```bash +git add MinecraftClient/Pathing/Execution/PathTransitionHints.cs \ + MinecraftClient/Pathing/Execution/PathSegment.cs \ + MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs \ + MinecraftClient.Tests/Pathing/Execution/PathTransitionHintsTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs +git commit -m "feat: add quantitative path transition hints" +``` + +--- + +### Task 2: Replace Threshold-Only Braking with Short-Horizon Candidate Scoring + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/TransitionInputProfile.cs` +- Create: `MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + +- [ ] **Step 1: Write the failing evaluator and planner tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TransitionLookaheadEvaluatorTests +{ + [Fact] + public void ChooseGroundProfile_PicksBrake_WhenTurnEntryCapsResidualSpeed() + { + 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.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.0, 0.035, true, true, false, true, 12) + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(1.34, 80.0, 0.5), + DeltaMovement = new Vec3d(0.156, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile( + current, + new Location(1.34, 80.0, 0.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.Brake, profile); + } + + [Fact] + public void ChooseGroundProfile_PicksCarry_WhenPrepareJumpNeedsRunUpSpeed() + { + 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.12, double.PositiveInfinity, false, true, true, false, 10), + PreserveSprint = true + }; + + var physics = new PlayerPhysics + { + Position = new Vec3d(1.02, 80.0, 0.5), + DeltaMovement = new Vec3d(0.086, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseGroundProfile( + current, + new Location(1.02, 80.0, 0.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.Carry, profile); + } + + [Fact] + public void ChooseAirProfile_PicksRelease_WhenLandingNeedsSlowStableEntry() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 118, max: 126); + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.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 physics = new PlayerPhysics + { + Position = new Vec3d(123.06, 80.92, 110.5), + DeltaMovement = new Vec3d(0.31, 0.0, 0.0), + OnGround = false, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile( + current, + new Location(123.06, 80.92, 110.5), + physics, + world); + + Assert.Equal(TransitionInputProfile.AirRelease, profile); + } +} +``` + +- [ ] **Step 2: Run the evaluator-focused tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~TransitionLookaheadEvaluatorTests|FullyQualifiedName~TransitionBrakingPlannerTests" -v minimal +``` + +Expected: FAIL with compile errors because `TransitionInputProfile` and `TransitionLookaheadEvaluator` do not exist yet. + +- [ ] **Step 3: Implement candidate profiles, lookahead scoring, and planner wiring** + +```csharp +// MinecraftClient/Pathing/Execution/TransitionInputProfile.cs +namespace MinecraftClient.Pathing.Execution +{ + internal enum TransitionInputProfile + { + Carry, + Coast, + Brake, + AirHoldForward, + AirRelease, + AirBrake + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + internal static class TransitionLookaheadEvaluator + { + internal static TransitionInputProfile ChooseGroundProfile(PathSegment segment, Location pos, PlayerPhysics physics, World world) + { + TransitionInputProfile[] candidates = + [ + TransitionInputProfile.Carry, + TransitionInputProfile.Coast, + TransitionInputProfile.Brake + ]; + + return ChooseBest(segment, pos, physics, world, candidates); + } + + internal static TransitionInputProfile ChooseAirProfile(PathSegment segment, Location pos, PlayerPhysics physics, World world) + { + TransitionInputProfile[] candidates = + [ + TransitionInputProfile.AirHoldForward, + TransitionInputProfile.AirRelease, + TransitionInputProfile.AirBrake + ]; + + return ChooseBest(segment, pos, physics, world, candidates); + } + + private static TransitionInputProfile ChooseBest(PathSegment segment, 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, pos, physics, world, candidate); + if (score < bestScore) + { + best = candidate; + bestScore = score; + } + } + + return best; + } + + private static double Score(PathSegment segment, Location pos, PlayerPhysics physics, World world, TransitionInputProfile candidate) + { + PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics); + var input = new MovementInput(); + double score = 0.0; + + for (int tick = 0; tick < segment.ExitHints.HorizonTicks; tick++) + { + input.Reset(); + ApplyCandidateInput(input, candidate, segment.PreserveSprint); + sim.ApplyInput(input); + sim.Tick(world); + } + + Location simPos = new(sim.Position.X, sim.Position.Y, sim.Position.Z); + double forwardSpeed = TemplateHelper.ProjectHorizontalSpeedAlongSegment(sim, segment); + + if (segment.ExitHints.RequireGrounded && !sim.OnGround) + score += 1000.0; + + if (segment.ExitHints.RequireStableFooting && + !TemplateHelper.IsSettledOnTargetBlock(simPos, segment.End, sim)) + { + score += 1000.0; + } + + if (forwardSpeed < segment.ExitHints.MinExitSpeed) + score += (segment.ExitHints.MinExitSpeed - forwardSpeed) * 200.0; + + if (forwardSpeed > segment.ExitHints.MaxExitSpeed) + score += (forwardSpeed - segment.ExitHints.MaxExitSpeed) * 200.0; + + score += TemplateHelper.HeadingPenaltyDegrees(sim.Yaw, segment.ExitHints.DesiredHeadingX, segment.ExitHints.DesiredHeadingZ); + score += Math.Abs(segment.End.X - simPos.X) + Math.Abs(segment.End.Z - simPos.Z); + + return score; + } + + private static void ApplyCandidateInput(MovementInput input, TransitionInputProfile candidate, bool preserveSprint) + { + switch (candidate) + { + case TransitionInputProfile.Carry: + case TransitionInputProfile.AirHoldForward: + input.Forward = true; + input.Sprint = preserveSprint; + break; + case TransitionInputProfile.Brake: + case TransitionInputProfile.AirBrake: + input.Back = true; + break; + case TransitionInputProfile.Coast: + case TransitionInputProfile.AirRelease: + default: + break; + } + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs +public static TransitionBrakingDecision Plan(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) +{ + TransitionInputProfile profile = physics.OnGround + ? TransitionLookaheadEvaluator.ChooseGroundProfile(current, pos, physics, world) + : TransitionLookaheadEvaluator.ChooseAirProfile(current, pos, physics, world); + + return profile switch + { + TransitionInputProfile.Carry => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint), + TransitionInputProfile.Coast => TransitionBrakingDecision.Coast, + TransitionInputProfile.Brake => TransitionBrakingDecision.Brake, + TransitionInputProfile.AirHoldForward => TransitionBrakingDecision.CarryMomentum(current.PreserveSprint), + TransitionInputProfile.AirRelease => TransitionBrakingDecision.Coast, + TransitionInputProfile.AirBrake => TransitionBrakingDecision.Brake, + _ => TransitionBrakingDecision.Coast + }; +} + +public static bool ShouldReleaseForwardInAir(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) +{ + if (!current.ExitHints.AllowAirBrake) + return false; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile(current, pos, physics, world); + return profile is TransitionInputProfile.AirRelease or TransitionInputProfile.AirBrake; +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +internal static PlayerPhysics ClonePhysicsForPlanning(PlayerPhysics physics) +{ + return new PlayerPhysics + { + Position = physics.Position, + DeltaMovement = physics.DeltaMovement, + Yaw = physics.Yaw, + Pitch = physics.Pitch, + OnGround = physics.OnGround, + HorizontalCollision = physics.HorizontalCollision, + VerticalCollision = physics.VerticalCollision, + VerticalCollisionBelow = physics.VerticalCollisionBelow, + FallDistance = physics.FallDistance, + StuckSpeedMultiplier = physics.StuckSpeedMultiplier, + Xxa = physics.Xxa, + Zza = physics.Zza, + Yya = physics.Yya, + Jumping = physics.Jumping, + Sprinting = physics.Sprinting, + Sneaking = physics.Sneaking, + CreativeFlying = physics.CreativeFlying, + InWater = physics.InWater, + IsUnderWater = physics.IsUnderWater, + InLava = physics.InLava, + OnClimbable = physics.OnClimbable, + HasSlowFalling = physics.HasSlowFalling, + HasLevitation = physics.HasLevitation, + LevitationAmplifier = physics.LevitationAmplifier, + MovementSpeed = physics.MovementSpeed + }; +} + +internal static double HeadingPenaltyDegrees(float yaw, int headingX, int headingZ) +{ + if (headingX == 0 && headingZ == 0) + return 0.0; + + float targetYaw = CalculateYaw(headingX, headingZ); + float delta = targetYaw - yaw; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta) / 10.0; +} +``` + +- [ ] **Step 4: Re-run the planner tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~TransitionLookaheadEvaluatorTests|FullyQualifiedName~TransitionBrakingPlannerTests" -v minimal +``` + +Expected: PASS with all evaluator and planner tests green. + +- [ ] **Step 5: Commit the planner upgrade** + +```bash +git add MinecraftClient/Pathing/Execution/TransitionInputProfile.cs \ + MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs \ + MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs \ + MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient.Tests/Pathing/Execution/TransitionLookaheadEvaluatorTests.cs \ + MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs +git commit -m "feat: score transition inputs with short-horizon lookahead" +``` + +--- + +### Task 3: Teach Templates to Hand Off Only When Exit Contracts Are Satisfied + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + +- [ ] **Step 1: Add failing convergence tests for turn-entry and jump-entry handoff** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs +[Fact] +public void WalkTemplate_TurnIntoParkour_CompletesOnlyWhenTurnEntryIsSlowAndJumpReady() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 120, max: 128); + + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(121.5, 80, 110.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.Turn, + ExitHints = new PathTransitionHints(0, 1, 0.08, 0.035, true, true, true, true, 12) + }; + var next = new PathSegment + { + Start = new Location(121.5, 80, 110.5), + End = new Location(121.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 template = new WalkTemplate(current, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 140, out Location finalPos); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, current.End)); + Assert.InRange(horizontalSpeed, 0.08, 0.20); +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs +[Fact] +public void SprintJumpTemplate_LandingRecoveryIntoTurn_CompletesWithLowResidualSpeed() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 118, max: 126); + FlatWorldTestBuilder.ClearBox(world, 118, 79, 108, 126, 90, 112); + FlatWorldTestBuilder.SetSolid(world, 120, 79, 110); + FlatWorldTestBuilder.SetSolid(world, 123, 79, 110); + FlatWorldTestBuilder.SetSolid(world, 123, 79, 111); + + var segment = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.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 = new Location(123.5, 80, 110.5), + End = new Location(123.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 160, out Location finalPos); + double horizontalSpeed = Math.Sqrt(physics.DeltaMovement.X * physics.DeltaMovement.X + physics.DeltaMovement.Z * physics.DeltaMovement.Z); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + Assert.InRange(horizontalSpeed, 0.0, 0.04); +} +``` + +- [ ] **Step 2: Run the convergence tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~GroundedTemplateConvergenceTests|FullyQualifiedName~SprintJumpTemplateScenarioTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: FAIL because grounded completion and airborne release still use coarse threshold logic. + +- [ ] **Step 3: Update grounded and airborne templates to obey `ExitHints`** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs +internal static bool ShouldComplete(PathSegment segment, Location pos, PlayerPhysics physics) +{ + if (segment.ExitHints.RequireJumpReady) + { + return physics.OnGround + && TemplateHelper.HasReachedSegmentEndPlane(pos, segment) + && TemplateHelper.ProjectHorizontalSpeedAlongSegment(physics, segment) >= segment.ExitHints.MinExitSpeed + && TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment.ExitHints.DesiredHeadingX, segment.ExitHints.DesiredHeadingZ) <= 1.0; + } + + if (segment.ExitHints.RequireStableFooting) + { + return physics.OnGround + && TemplateHelper.IsSettledOnTargetBlock(pos, segment.End, physics) + && TemplateHelper.HeadingPenaltyDegrees(physics.Yaw, segment.ExitHints.DesiredHeadingX, segment.ExitHints.DesiredHeadingZ) <= 2.0; + } + + return segment.ExitTransition switch + { + PathTransitionType.ContinueStraight => TemplateHelper.IsNear(pos, segment.End, horizThresholdSq: 0.09), + PathTransitionType.PrepareJump => TemplateHelper.HasReachedSegmentEndPlane(pos, segment) + && TemplateHelper.ProjectHorizontalSpeedAlongSegment(physics, segment) >= segment.ExitHints.MinExitSpeed, + _ => TemplateHelper.HasReachedSegmentEndPlane(pos, segment) + }; +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs +else if (horizDistSq > 0.01) +{ + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + if (_hasFallen || YawDifference(physics.Yaw, targetYaw) <= PreDropYawToleranceDeg) + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + + if (!_hasFallen && _segment.ExitHints.AllowAirBrake && decision == TransitionBrakingDecision.Coast) + { + input.Forward = false; + input.Sprint = false; + } + else + { + TemplateHelper.ApplyDecision(input, decision); + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +private bool ShouldReleaseInAir(Location pos, PlayerPhysics physics, World world) +{ + if (!_segment.ExitHints.AllowAirBrake) + return false; + + TransitionInputProfile profile = TransitionLookaheadEvaluator.ChooseAirProfile(_segment, pos, physics, world); + return profile is TransitionInputProfile.AirRelease or TransitionInputProfile.AirBrake; +} + +case Phase.Landing: + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + + if (physics.OnGround && GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + break; +``` + +- [ ] **Step 4: Re-run the convergence tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~GroundedTemplateConvergenceTests|FullyQualifiedName~SprintJumpTemplateScenarioTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: PASS with the turn-entry and landing-entry regressions green. + +- [ ] **Step 5: Commit the template handoff slice** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs \ + MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs \ + MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs \ + MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +git commit -m "feat: honor transition entry contracts in templates" +``` + +--- + +### Task 4: Modernize the 1.21.11 Live Regression Harness and Document the New Semantics + +**Files:** +- Modify: `tools/test-transition-braking.sh` +- Modify: `tools/test-pathing-template-regressions.sh` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Rewrite the live harness around `mcc-build`, `mcc-debug`, and `mcc-cmd`** + +```bash +# tools/test-transition-braking.sh +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +SESSION="${2:-brake-lookahead}" +USERNAME="${3:-$(_mcc_resolve_username "$SESSION")}" + +send_mcc() { + mcc-cmd --session "$SESSION" "$1" +} + +start_mcc() { + mcc-build >/dev/null + mcc-debug -v "$VERSION" --file-input --session "$SESSION" --username "$USERNAME" --no-build --debug-on >/dev/null + mc-rcon "op $USERNAME" >/dev/null +} + +capture_debug_location() { + local root="${TMPDIR:-/tmp}/mcc-debug/$SESSION" + local log="$root/mcc-debug.log" + local start_line + start_line="$(wc -l < "$log")" + send_mcc "debug state" + for _ in $(seq 1 10); do + if tail -n +"$((start_line + 1))" "$log" | grep -Fq "Location:"; then + python3 - "$log" "$start_line" <<'PY' +import pathlib +import re +import sys + +path = pathlib.Path(sys.argv[1]) +start_line = int(sys.argv[2]) +text = "\n".join(path.read_text(errors="ignore").splitlines()[start_line:]) +match = re.findall(r"Location:\s+([-\d.]+),\s+([-\d.]+),\s+([-\d.]+)", text) +if not match: + raise SystemExit("No Location line found") +x, y, z = match[-1] +print(f"{x} {y} {z}") +PY + return 0 + fi + sleep 1 + done + return 1 +} +``` + +- [ ] **Step 2: Add one straight-stop scenario and one turn-into-jump scenario to the harness** + +```bash +# tools/test-transition-braking.sh +run_turn_into_jump() { + echo "== Turn into jump runway ==" + mc-rcon "fill 120 79 110 126 79 114 stone" >/dev/null + mc-rcon "fill 120 80 110 126 85 114 air" >/dev/null + mc-rcon "setblock 123 79 112 air" >/dev/null + mc-rcon "setblock 124 79 112 stone" >/dev/null + mc-rcon "tp $USERNAME 120.5 80 110.5" >/dev/null + sleep 2 + + send_mcc "goto 124 80 112" + sleep 6 + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo "Final location: $x $y $z" + + python3 - <<'PY' "$x" "$z" +import sys +x = float(sys.argv[1]) +z = float(sys.argv[2]) +if not (123.20 <= x <= 124.10 and 111.80 <= z <= 112.60): + raise SystemExit(f"Unexpected turn-into-jump finish: ({x:.2f}, {z:.2f})") +PY +} +``` + +- [ ] **Step 3: Update the pathfinding research doc to describe entry contracts** + +```md + +## Transition Entry Contracts + +Path execution no longer aims for a visual block center as the primary success rule. +Instead, each `PathSegment` carries quantitative exit hints that describe what the next +segment needs: + +- desired heading at handoff +- minimum exit speed when the next action is a jump takeoff +- maximum exit speed when the next action is a turn or final stop +- whether stable grounded footing is required before handoff +- whether airborne forward release is allowed before landing + +This keeps MCC physically honest while still making segment boundaries precise enough +for chained turns and jumps on 1.21.11. +``` + +- [ ] **Step 4: Run the full validation loop** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal +source tools/mcc-env.sh && mcc-build +source tools/mcc-env.sh && bash tools/test-transition-braking.sh 1.21.11-Vanilla +source tools/mcc-env.sh && bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected: + +- `dotnet test` reports all pathing execution tests passing +- `mcc-build` exits `0` +- `tools/test-transition-braking.sh` prints `All transition braking checks passed.` +- `tools/test-pathing-template-regressions.sh` exits `0` + +- [ ] **Step 5: Commit the harness and documentation updates** + +```bash +git add tools/test-transition-braking.sh \ + tools/test-pathing-template-regressions.sh \ + docs/guide/pathfinding-research.md +git commit -m "test: validate lookahead path transitions on 1.21.11" +``` + +--- + +## Self-Review + +**Spec coverage** + +- "planner should know whether the next step continues or turns": covered by Task 1 transition hints and Task 2 evaluator scoring +- "braking can start on the previous step": covered by Task 2 candidate evaluation and Task 3 grounded template handoff rules +- "airborne forward release should be planned": covered by Task 2 `ChooseAirProfile()` and Task 3 `SprintJumpTemplate` +- "continue with the new dev workflow on 1.21.11": covered by Task 4 harness modernization and validation commands + +**Placeholder scan** + +- No `TODO`, `TBD`, or "implement later" markers remain +- Every code-changing step includes concrete code blocks +- Every verification step includes exact commands and expected results + +**Type consistency** + +- `PathTransitionHints` is the only new segment metadata type +- `TransitionInputProfile` is the only new candidate-input enum +- `TransitionLookaheadEvaluator` is the only new evaluator type used by `TransitionBrakingPlanner` diff --git a/docs/superpowers/plans/2026-04-12-pathing-template-convergence.md b/docs/superpowers/plans/2026-04-12-pathing-template-convergence.md new file mode 100644 index 0000000000..0a693db814 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-pathing-template-convergence.md @@ -0,0 +1,993 @@ +# Pathing Template Convergence 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:** Make every path segment MCC agrees to execute stop safely inside the target block support, reject parkour moves that are not yet reliable, and prove traverse, ascend, descend, climb, fall, and sprint-jump behavior on local 1.21.11. + +**Architecture:** Keep A* and the existing move catalog mostly intact, but tighten reliability at two boundaries. On the planning side, adopt Baritone-style conservative parkour admissibility so MCC stops accepting jumps it cannot execute consistently. On the execution side, replace center-hunting with support-footprint completion and add a shared grounded-segment controller so walk, ascend, descend, and sprint-jump all use the same transition rules. + +**Tech Stack:** C# 14 / .NET 10, MCC `PlayerPhysics`, xUnit deterministic regression tests, local bash harnesses under `tools/`, local offline Minecraft 1.21.11 server via `tools/mcc-env.sh`. + +--- + +## Execution Context + +This plan assumes implementation happens in a dedicated worktree even though the current investigation ran in the main workspace. Do not tune flat-stop precision toward exact block center. The success bar is simpler: the player may finish anywhere inside the target block support footprint, but must not drift past the edge once the segment reports success. + +## Scope + +In scope: + +- tighten parkour admissibility until accepted jumps are reliable +- converge grounded template completion rules across walk, ascend, descend, and sprint-jump landing +- preserve working climb and fall behavior with regression coverage +- add deterministic simulation tests and real-server regression scripts + +Out of scope for this pass: + +- expanding the parkour move catalog beyond moves we can prove reliable +- changing A* heuristics or node expansion rules unrelated to movement correctness +- making `Shift` a full SafeWalk feature for all contexts + +## File Structure + +### New files + +- `MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs` + Shared support-footprint math. Answers "is the player's 0.6-wide footprint still fully inside the target block?" and "would current velocity carry it outside next tick?" +- `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` + Shared grounded transition logic for walk, ascend, descend, and sprint-jump landing. +- `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` + Conservative parkour admissibility helper: run-up, shoulder clearance, overshoot safety, and landing validation. +- `MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs` + Deterministic loop that drives `IActionTemplate`, `MovementInput`, and `PlayerPhysics` against a test world. +- `MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs` + Unit tests for support-footprint completion rules. +- `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` + Simulation tests for walk, ascend, and descend transition behavior. +- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + Simulation tests for parkour landing, turn preparation, and accepted side-wall jumps. +- `MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs` + Simulation smoke tests for climb and fall so convergence work does not regress them. +- `MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs` + Planning-time admissibility tests for `MoveParkour`. +- `tools/test-pathing-template-regressions.sh` + Real-server regression harness for local 1.21.11. + +### Modified files + +- `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + Route support-footprint checks through the new helper and expose shared heading/progress helpers. +- `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` + Stop using settle-at-center rules for `PrepareJump`, `Turn`, and `FinalStop`. +- `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` + Use shared grounded completion after landing and treat `PrepareJump` as a handoff, not a settle. +- `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` + Use shared landing recovery and block-support completion instead of center-hunting. +- `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + Split takeoff into explicit phases, release input earlier in air when needed, and finish on target support instead of target center. +- `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` + Replace ad hoc run-up checks with shared conservative feasibility logic. +- `MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs` + Add helpers to place blocks, carve air, and build side-wall / stair / ladder / gap scenarios. +- `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + Add cases that match new landing and release thresholds where needed. +- `docs/guide/pathfinding-research.md` + Document the reliability-first rule: accepted moves must be executable, support-footprint completion is sufficient, and unsupported parkour shapes are rejected. + +--- + +### Task 1: Add Support-Footprint Completion Rules + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + +- [ ] **Step 1: Write the failing support-footprint tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TemplateFootingTests +{ + [Fact] + public void IsFootprintInsideTargetBlock_ReturnsTrue_WhenPlayerIsNearEdgeButStillInside() + { + bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock( + new Location(10.69, 80.0, 4.50), + new Location(10.50, 80.0, 4.50)); + + Assert.True(inside); + } + + [Fact] + public void IsFootprintInsideTargetBlock_ReturnsFalse_WhenPlayerCrossesBlockEdge() + { + bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock( + new Location(10.81, 80.0, 4.50), + new Location(10.50, 80.0, 4.50)); + + Assert.False(inside); + } + + [Fact] + public void WillLeaveTargetBlockNextTick_ReturnsTrue_WhenVelocityWouldCarryPastEdge() + { + var physics = new PlayerPhysics + { + Position = new Vec3d(10.67, 80.0, 4.50), + DeltaMovement = new Vec3d(0.060, 0.0, 0.0), + OnGround = true + }; + + bool exitsNextTick = TemplateFootingHelper.WillLeaveTargetBlockNextTick( + new Location(10.67, 80.0, 4.50), + physics, + new Location(10.50, 80.0, 4.50)); + + Assert.True(exitsNextTick); + } +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter TemplateFootingTests -v minimal +``` + +Expected: FAIL with compile errors because `TemplateFootingHelper` and the new helper methods do not exist yet. + +- [ ] **Step 3: Implement the support-footprint helper and route `TemplateHelper` through it** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates; + +internal static class TemplateFootingHelper +{ + private const double HalfWidth = PhysicsConsts.PlayerWidth / 2.0; + + internal static bool IsFootprintInsideTargetBlock(Location pos, Location target, double epsilon = 1.0E-4) + { + double minX = pos.X - HalfWidth; + double maxX = pos.X + HalfWidth; + double minZ = pos.Z - HalfWidth; + double maxZ = pos.Z + HalfWidth; + + double blockMinX = Math.Floor(target.X); + double blockMaxX = blockMinX + 1.0; + double blockMinZ = Math.Floor(target.Z); + double blockMaxZ = blockMinZ + 1.0; + + return minX >= blockMinX - epsilon + && maxX <= blockMaxX + epsilon + && minZ >= blockMinZ - epsilon + && maxZ <= blockMaxZ + epsilon; + } + + internal static bool WillLeaveTargetBlockNextTick(Location pos, PlayerPhysics physics, Location target, double epsilon = 1.0E-4) + { + Location next = new( + pos.X + physics.DeltaMovement.X, + pos.Y, + pos.Z + physics.DeltaMovement.Z); + return !IsFootprintInsideTargetBlock(next, target, epsilon); + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +internal static bool IsSettledOnTargetBlock(Location pos, Location target, PlayerPhysics physics, + double speedThresholdSq = 0.0016) +{ + double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z; + + if (!TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, target)) + return false; + + if (TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, target)) + return false; + + return horizontalSpeedSq <= speedThresholdSq; +} +``` + +- [ ] **Step 4: Re-run the support-footprint tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter TemplateFootingTests -v minimal +``` + +Expected: PASS with `3 Passed`. + +- [ ] **Step 5: Commit the support-footprint groundwork** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/TemplateFootingHelper.cs \ + MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient.Tests/Pathing/Execution/TemplateFootingTests.cs +git commit -m "feat: add support-aware template completion checks" +``` + +--- + +### Task 2: Tighten Parkour Admissibility to the Reliable Subset + +**Files:** +- Create: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` +- Create: `MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs` +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs` + +- [ ] **Step 1: Write the failing `MoveParkour` admissibility tests** + +```csharp +// MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves.Impl; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +public sealed class MoveParkourTests +{ + [Fact] + public void Calculate_RejectsThreeByOneSideWall_WhenRunUpIsMissing() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 2); + FlatWorldTestBuilder.SetSolid(world, 5, 79, 3); + FlatWorldTestBuilder.FillSolid(world, 4, 79, 2, 4, 81, 2); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveParkour(3, 1); + MoveResult result = default; + + move.Calculate(ctx, 2, 80, 2, ref result); + + Assert.True(result.IsImpossible); + } + + [Fact] + public void Calculate_AcceptsTwoByOneSideWall_WhenTakeoffAndLandingAreClear() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 2); + FlatWorldTestBuilder.SetSolid(world, 4, 79, 3); + FlatWorldTestBuilder.FillSolid(world, 4, 79, 2, 4, 81, 2); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveParkour(2, 1); + MoveResult result = default; + + move.Calculate(ctx, 2, 80, 2, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(4, result.DestX); + Assert.Equal(80, result.DestY); + Assert.Equal(3, result.DestZ); + } + + [Fact] + public void Calculate_RejectsDiagonalJump_WhenTakeoffShoulderIsBlocked() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 2); + FlatWorldTestBuilder.SetSolid(world, 4, 79, 4); + FlatWorldTestBuilder.SetSolid(world, 3, 80, 2); + + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveParkour(2, 2); + MoveResult result = default; + + move.Calculate(ctx, 2, 80, 2, ref result); + + Assert.True(result.IsImpossible); + } +} +``` + +- [ ] **Step 2: Run the parkour admissibility tests and watch them fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter MoveParkourTests -v minimal +``` + +Expected: FAIL because current `MoveParkour` only checks one behind-block for run-up and does not centralize side-clearance logic. + +- [ ] **Step 3: Extract conservative feasibility checks and wire `MoveParkour` through them** + +```csharp +// MinecraftClient/Pathing/Moves/ParkourFeasibility.cs +using System; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves; + +internal static class ParkourFeasibility +{ + internal static int RequiredRunUpBlocks(int xOffset, int zOffset, int yDelta) + { + double horizDist = Math.Sqrt((double)(xOffset * xOffset + zOffset * zOffset)); + if (yDelta > 0 || horizDist >= 4.0) + return 2; + if (horizDist >= 3.0) + return 1; + return 0; + } + + internal static bool HasRunUp(CalculationContext ctx, int x, int y, int z, int xOffset, int zOffset, int yDelta) + { + int stepX = Math.Sign(xOffset); + int stepZ = Math.Sign(zOffset); + int required = RequiredRunUpBlocks(xOffset, zOffset, yDelta); + + for (int i = 1; i <= required; i++) + { + int rx = x - stepX * i; + int rz = z - stepZ * i; + if (!ctx.CanWalkOn(rx, y - 1, rz) + || !ctx.CanWalkThrough(rx, y, rz) + || !ctx.CanWalkThrough(rx, y + 1, rz)) + { + return false; + } + } + + return true; + } + + internal static bool HasDiagonalTakeoffClearance(CalculationContext ctx, int x, int y, int z, int stepX, int stepZ) + { + return ctx.CanWalkThrough(x + stepX, y, z) + && ctx.CanWalkThrough(x + stepX, y + 1, z) + && ctx.CanWalkThrough(x, y, z + stepZ) + && ctx.CanWalkThrough(x, y + 1, z + stepZ); + } + + internal static bool HasOvershootClearance(CalculationContext ctx, int x, int y, int z) + { + return ctx.CanWalkThrough(x, y, z) && ctx.CanWalkThrough(x, y + 1, z); + } +} +``` + +```csharp +// MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs +if (!ParkourFeasibility.HasRunUp(ctx, x, y, z, XOffset, ZOffset, _yDelta)) +{ + result.SetImpossible(); + return; +} + +if (xAbs > 0 && zAbs > 0 && !ParkourFeasibility.HasDiagonalTakeoffClearance(ctx, x, y, z, xSign, zSign)) +{ + result.SetImpossible(); + return; +} + +int overX = destX + xSign; +int overZ = destZ + zSign; +if (!ParkourFeasibility.HasOvershootClearance(ctx, overX, destY, overZ)) +{ + result.SetImpossible(); + return; +} +``` + +- [ ] **Step 4: Re-run the parkour admissibility tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter MoveParkourTests -v minimal +``` + +Expected: PASS with `3 Passed`. + +- [ ] **Step 5: Commit the planner hardening** + +```bash +git add MinecraftClient/Pathing/Moves/ParkourFeasibility.cs \ + MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs \ + MinecraftClient.Tests/Pathing/Moves/MoveParkourTests.cs \ + MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs +git commit -m "feat: tighten parkour move admissibility" +``` + +--- + +### Task 3: Converge Walk, Ascend, and Descend on Shared Grounded Transition Rules + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs` + +- [ ] **Step 1: Write the failing simulation tests for grounded segment handoff** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class GroundedTemplateConvergenceTests +{ + [Fact] + public void WalkTemplate_FinalStop_Completes_WhenFootprintStaysInsideTargetBlock() + { + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 80, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } + + [Fact] + public void WalkTemplate_PrepareJump_CompletesWithoutSettlingOnRunUpBlock() + { + 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, + 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: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 40, out _); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(physics.DeltaMovement.X > 0.05); + } + + [Fact] + public void DescendTemplate_LandingRecovery_CompletesOnLandingBlock() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + FlatWorldTestBuilder.ClearBox(world, 1, 80, 0, 1, 80, 0); + FlatWorldTestBuilder.SetSolid(world, 1, 78, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 79, 0.5), + MoveType = MoveType.Descend, + ExitTransition = PathTransitionType.LandingRecovery + }; + + var template = new DescendTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 120, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } +} +``` + +- [ ] **Step 2: Run the grounded simulation tests and watch them fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter GroundedTemplateConvergenceTests -v minimal +``` + +Expected: FAIL because there is no simulation runner yet and current templates still use settle-at-center rules for `PrepareJump` and landing recovery. + +- [ ] **Step 3: Add a shared grounded controller and migrate walk / ascend / descend to it** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates; + +internal static class GroundedSegmentController +{ + internal static void Apply(PathSegment segment, PathSegment? nextSegment, Location pos, PlayerPhysics physics, MovementInput input, World world) + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(segment, nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + if (decision.HoldBack) + TemplateHelper.FaceSegmentHeading(physics, segment); + } + + internal static bool ShouldComplete(PathSegment segment, Location pos, PlayerPhysics physics) + { + return segment.ExitTransition switch + { + PathTransitionType.ContinueStraight => TemplateHelper.IsNear(pos, segment.End, horizThresholdSq: 0.09), + PathTransitionType.PrepareJump => TemplateHelper.HasReachedSegmentEndPlane(pos, segment), + _ => TemplateHelper.IsSettledOnTargetBlock(pos, segment.End, physics) + }; + } +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class TemplateSimulationRunner +{ + internal static PlayerPhysics CreateGroundedPhysics(Location start, float yaw) + { + return new PlayerPhysics + { + Position = new Vec3d(start.X, start.Y, start.Z), + DeltaMovement = Vec3d.Zero, + OnGround = true, + MovementSpeed = 0.1f, + Yaw = yaw + }; + } + + internal static TemplateState Run(IActionTemplate template, PlayerPhysics physics, World world, int maxTicks, out Location finalPos) + { + var input = new MovementInput(); + TemplateState state = TemplateState.InProgress; + + for (int tick = 0; tick < maxTicks && state == TemplateState.InProgress; tick++) + { + input.Reset(); + Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z); + state = template.Tick(pos, physics, input, world); + physics.ApplyInput(input); + physics.Tick(world); + } + + finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z); + return state; + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs +GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + +if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +internal static bool HasReachedSegmentEndPlane(Location pos, PathSegment segment) +{ + double dx = pos.X - segment.End.X; + double dz = pos.Z - segment.End.Z; + return dx * segment.HeadingX + dz * segment.HeadingZ >= -0.05; +} +``` + +- [ ] **Step 4: Re-run the grounded simulation tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter GroundedTemplateConvergenceTests -v minimal +``` + +Expected: PASS with `3 Passed`. + +- [ ] **Step 5: Commit the grounded-template convergence work** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs \ + MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs \ + MinecraftClient.Tests/Pathing/Execution/TemplateSimulationRunner.cs \ + MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs \ + MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs +git commit -m "feat: converge grounded path execution templates" +``` + +--- + +### Task 4: Rework Sprint Jump Execution Around Committed Takeoff and Support-Aware Landing + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + +- [ ] **Step 1: Write the failing sprint-jump scenario tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class SprintJumpTemplateScenarioTests +{ + [Fact] + public void SprintJumpTemplate_ParkourIntoTurn_LandsInsideTargetSupport() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 1, 79, 0, 2, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 3, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 3, 79, 1); + + var current = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(3.5, 80, 0.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.LandingRecovery + }; + var next = new PathSegment + { + Start = new Location(3.5, 80, 0.5), + End = new Location(3.5, 80, 1.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(current, next); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 270f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 80, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, current.End)); + } + + [Fact] + public void SprintJumpTemplate_TwoByOneSideWall_Completes() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 16); + FlatWorldTestBuilder.ClearBox(world, 1, 79, 0, 1, 79, 0); + FlatWorldTestBuilder.SetSolid(world, 2, 79, 1); + FlatWorldTestBuilder.FillSolid(world, 2, 79, 0, 2, 81, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(2.5, 80, 1.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 315f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 80, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); + } +} +``` + +- [ ] **Step 2: Run the sprint-jump scenario tests and confirm they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter SprintJumpTemplateScenarioTests -v minimal +``` + +Expected: FAIL because the current template still overshoots landing blocks and treats landing recovery as a late braking problem instead of a committed takeoff plus controlled handoff. + +- [ ] **Step 3: Introduce explicit jump phases and support-aware landing completion** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +private enum Phase +{ + Approach, + CommitJump, + Airborne, + LandingRecovery +} + +case Phase.Approach: + input.Forward = true; + input.Sprint = true; + if (physics.OnGround && YawDifference(physics.Yaw, targetYaw) < YawToleranceDeg && ReadyForTakeoff(pos)) + { + _phase = Phase.CommitJump; + } + break; + +case Phase.CommitJump: + input.Forward = true; + input.Sprint = true; + input.Jump = physics.OnGround; + if (!physics.OnGround) + { + _leftGround = true; + _phase = Phase.Airborne; + } + break; + +case Phase.Airborne: + bool releaseNow = TransitionBrakingPlanner.ShouldReleaseForwardInAir(_segment, _nextSegment, pos, physics) + || TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, ExpectedEnd); + input.Forward = !releaseNow; + input.Sprint = !releaseNow; + if (_leftGround && physics.OnGround) + _phase = Phase.LandingRecovery; + break; + +case Phase.LandingRecovery: + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + break; +``` + +- [ ] **Step 4: Re-run sprint-jump tests plus braking planner tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "SprintJumpTemplateScenarioTests|TransitionBrakingPlannerTests" -v minimal +``` + +Expected: PASS with all sprint-jump and braking tests green. + +- [ ] **Step 5: Commit the sprint-jump convergence** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs \ + MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs +git commit -m "feat: stabilize sprint jump execution transitions" +``` + +--- + +### Task 5: Add Regression Coverage for Climb / Fall and Real-Server Template Matrix + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs` +- Create: `tools/test-pathing-template-regressions.sh` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Write the remaining simulation smoke tests and the local server harness** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class ClimbFallTemplateTests +{ + [Fact] + public void ClimbTemplate_UpwardMove_StillCompletes() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 0, max: 8); + FlatWorldTestBuilder.FillSolid(world, 0, 79, 0, 0, 82, 0); + FlatWorldTestBuilder.SetClimbable(world, 0, 80, 0); + FlatWorldTestBuilder.SetClimbable(world, 0, 81, 0); + + var segment = new PathSegment + { + Start = new Location(0.5, 80, 0.5), + End = new Location(0.5, 81, 0.5), + MoveType = MoveType.Climb + }; + + var template = new ClimbTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 120, out _); + + Assert.Equal(TemplateState.Complete, state); + } +} +``` + +```bash +#!/usr/bin/env bash +# tools/test-pathing-template-regressions.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +INPUT_FILE="$REPO_ROOT/mcc_input.txt" +LOG_DIR="${TMPDIR:-/tmp}/mcc-debug" +LOG_FILE="$LOG_DIR/mcc-template-regressions.log" +CFG="$LOG_DIR/MinecraftClient.template-regressions.ini" + +send_mcc() { + printf '%s\n' "$1" >> "$INPUT_FILE" +} + +wait_for_log() { + local pattern="$1" + local timeout="${2:-20}" + for _ in $(seq 1 "$timeout"); do + if grep -Fq "$pattern" "$LOG_FILE"; then + return 0 + fi + sleep 1 + done + return 1 +} + +run_case() { + local name="$1" + local command="$2" + local expected="$3" + echo "== $name ==" + : > "$LOG_FILE" + send_mcc "$command" + wait_for_log "$expected" 20 + grep -E "\\[PathMgr\\]|\\[PathExec\\]|\\[A\\*\\]" "$LOG_FILE" | tail -20 +} + +mcc-preflight "$VERSION" >/dev/null +mc-start "$VERSION" >/dev/null +mc-wait-ready "$VERSION" 60 >/dev/null +echo "Prepare temp config at $CFG before first run" +echo "Use this harness to validate:" +echo "1. flat final stop" +echo "2. parkour into L turn" +echo "3. 2x1 side wall parkour" +echo "4. 3x1 no-run-up rejection" +echo "5. ascend + descend + climb smoke" +``` + +- [ ] **Step 2: Run the full unit suite plus the real-server matrix** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj -v minimal +dotnet build MinecraftClient.sln -c Release +bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected: + +- unit tests: PASS +- build: PASS +- real server: positive evidence that flat final stop, parkour into turn, accepted 2x1 side-wall, and mixed non-parkour segments complete +- real server: positive evidence that rejected parkour shapes are rejected up front instead of failing mid-execution + +- [ ] **Step 3: Document the new reliability rule** + +```md + +## Reliability-First Execution Rule + +MCC no longer treats block-center precision as the stop criterion for path execution. +A segment is considered safely complete when the player's full support footprint remains +inside the destination block and current velocity would not carry it beyond the edge on +the next tick. + +For parkour, planning is intentionally conservative: + +- if a jump shape is not covered by deterministic simulation plus local 1.21.11 regression + evidence, reject it during planning +- if a jump is accepted, execution must land on supported destination footprint without + relying on replan to rescue overshoot +``` + +- [ ] **Step 4: Re-run the docs-adjacent validation commands** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj -v minimal +dotnet build MinecraftClient.sln -c Release +``` + +Expected: PASS. No code or docs edits in this task should break the test suite or build. + +- [ ] **Step 5: Commit the regression matrix and documentation** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/ClimbFallTemplateTests.cs \ + tools/test-pathing-template-regressions.sh \ + docs/guide/pathfinding-research.md +git commit -m "test: add pathing template regression matrix" +``` + +--- + +## Verification Checklist + +Before calling this project done, the implementing agent must have fresh evidence for all of the following: + +- `MoveParkourTests` passes +- `TemplateFootingTests` passes +- `GroundedTemplateConvergenceTests` passes +- `SprintJumpTemplateScenarioTests` passes +- `ClimbFallTemplateTests` passes +- full `MinecraftClient.Tests` project passes +- `dotnet build MinecraftClient.sln -c Release` passes +- `tools/test-pathing-template-regressions.sh 1.21.11-Vanilla` shows positive runtime evidence for: + - flat final stop stays within target block support + - parkour into L-turn completes without rescue replan + - accepted 2x1 side-wall jump completes + - rejected 3x1 no-run-up shape is refused by planning + - mixed ascend / descend / climb route still completes + +## Coverage Check + +This plan covers every user-facing requirement from the current thread: + +- Flat stopping is no longer centered around exact block center. +- Success is defined as not leaving the block support footprint. +- Complex parkour issues discovered in local 1.21.11 testing are addressed. +- All current template families are included, either as changed code or protected by regression tests. +- Real local server validation remains part of the definition of done. diff --git a/docs/superpowers/plans/2026-04-12-pathing-transition-braking.md b/docs/superpowers/plans/2026-04-12-pathing-transition-braking.md new file mode 100644 index 0000000000..9de75eca01 --- /dev/null +++ b/docs/superpowers/plans/2026-04-12-pathing-transition-braking.md @@ -0,0 +1,1640 @@ +# Pathing Transition Braking 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:** Build next-segment-aware path execution that clears stale input on segment completion and uses predictive braking / momentum carry so MCC can enter turns, jumps, and final stops precisely on 1.21.11. + +**Architecture:** Keep A* pathfinding unchanged and upgrade only the execution layer. First add a small regression test harness, then annotate segments with transition intent, add a deterministic braking planner, and finally let templates use that planner to either preserve momentum, coast, or brake based on the next segment. + +**Tech Stack:** C# 14 / .NET 10, MCC `PlayerPhysics`, xUnit for deterministic regression tests, existing `tools/mcc-env.sh` + local 1.21.11 server harness for end-to-end validation. + +--- + +## Execution Context + +This plan assumes implementation happens in a dedicated worktree. Do not edit the repo-root `MinecraftClient.ini`; use the existing debug harness and temporary configs under `/tmp/mcc-debug/`. + +## File Structure + +### New files + +- `MinecraftClient.Tests/MinecraftClient.Tests.csproj` + Test project for path-execution and braking regressions. +- `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs` + Locks in the stale-input regression where a completed segment still leaves `Forward`/`Sprint` set. +- `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` + Verifies next-segment transition classification. +- `MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs` + Minimal deterministic world builder for stone-floor braking tests. +- `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` + Verifies coasting, back-braking, and airborne forward release decisions. +- `MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs` + Verifies template-level use of the planner. +- `MinecraftClient/Pathing/Execution/PathTransitionType.cs` + Enum describing the exit intent of a segment. +- `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` + Converts `PathNode` paths into `PathSegment` lists with transition metadata. +- `MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs` + Immutable result of the braking planner. +- `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + Predictive stop-distance and airborne-release logic shared by templates. +- `tools/test-transition-braking.sh` + Local 1.21.11 regression script for flat-stop and parkour-into-turn scenarios. + +### Modified files + +- `MinecraftClient.sln` + Add the new test project. +- `MinecraftClient/Pathing/Execution/PathSegment.cs` + Add heading and transition metadata to segments. +- `MinecraftClient/Pathing/Execution/IActionTemplate.cs` + Pass `World` into template ticks so braking decisions can read friction. +- `MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs` + Construct templates with the current and next segment. +- `MinecraftClient/Pathing/Execution/PathExecutor.cs` + Clear inputs on completion/failure, pass `World`, and wire next-segment context into templates. +- `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` + Swap `PathSegment.FromPath` for the new builder and pass `World` to the executor. +- `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + Add helpers for settled-state checks and applying braking decisions. +- `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` + Use predictive braking for final stops and turns. +- `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` + Preserve takeoff until the jump is done, then settle according to the next segment. +- `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` + Use post-landing braking for turns/final stops and preserve momentum for straight continuations. +- `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + Release `Forward`/`Sprint` early in the air when the next segment needs a stop or turn. +- `MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs` + Signature-only change to accept `World`. +- `MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs` + Signature-only change to accept `World`. +- `docs/guide/pathfinding-research.md` + Document transition-aware braking and how it differs from Baritone’s “goal block occupancy” semantics. + +--- + +### Task 1: Add the Regression Harness and Fix Stale Input on Completion + +**Files:** +- Create: `MinecraftClient.Tests/MinecraftClient.Tests.csproj` +- Create: `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs` +- Modify: `MinecraftClient.sln` +- Modify: `MinecraftClient/Pathing/Execution/PathExecutor.cs` + +- [ ] **Step 1: Write the failing test project and failing completion regression** + +```xml + + + + net10.0 + enable + enable + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathExecutorCompletionTests +{ + [Fact] + public void Tick_ClearsMovementInput_WhenSegmentCompletes() + { + var executor = new PathExecutor(new List + { + new() + { + Start = new Location(0.5, 80, 0.5), + End = new Location(1.5, 80, 0.5), + MoveType = MoveType.Traverse + } + }); + + var physics = new PlayerPhysics + { + Yaw = 270f, + Pitch = 0f + }; + var input = new MovementInput(); + var pos = new Location(1.45, 80, 0.5); + + PathExecutorState state = executor.Tick(pos, physics, input); + + Assert.Equal(PathExecutorState.Complete, state); + Assert.False(input.Forward); + Assert.False(input.Sprint); + Assert.False(input.Jump); + Assert.False(input.Back); + } +} +``` + +- [ ] **Step 2: Add the test project to the solution and run the test to verify it fails** + +Run: + +```bash +dotnet sln MinecraftClient.sln add MinecraftClient.Tests/MinecraftClient.Tests.csproj +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter Tick_ClearsMovementInput_WhenSegmentCompletes -v minimal +``` + +Expected: FAIL because `PathExecutor.Tick()` returns `Complete` while `input.Forward` is still `true`. + +- [ ] **Step 3: Write the minimal implementation in the executor** + +```csharp +// MinecraftClient/Pathing/Execution/PathExecutor.cs +public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input) +{ + if (_currentTemplate is null) + { + input.Reset(); + return PathExecutorState.Complete; + } + + var state = _currentTemplate.Tick(pos, physics, input); + + switch (state) + { + case TemplateState.Complete: + input.Reset(); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})"); + _currentIndex++; + if (_currentIndex >= _segments.Count) + { + _currentTemplate = null; + _debugLog?.Invoke("[PathExec] All segments complete!"); + return PathExecutorState.Complete; + } + AdvanceToNextSegment(); + return PathExecutorState.InProgress; + + case TemplateState.Failed: + input.Reset(); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " + + $"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})"); + return PathExecutorState.Failed; + + default: + return PathExecutorState.InProgress; + } +} +``` + +- [ ] **Step 4: Run the test project and make sure the regression passes** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter Tick_ClearsMovementInput_WhenSegmentCompletes -v minimal +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.sln \ + MinecraftClient.Tests/MinecraftClient.Tests.csproj \ + MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs \ + MinecraftClient/Pathing/Execution/PathExecutor.cs +git commit -m "test: lock path executor completion input reset" +``` + +### Task 2: Add Transition Metadata to Path Segments + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` +- Create: `MinecraftClient/Pathing/Execution/PathTransitionType.cs` +- Create: `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegment.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` + +- [ ] **Step 1: Write failing tests for transition classification** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathSegmentBuilderTests +{ + [Fact] + public void FromPath_AnnotatesStraightTraverse_AsContinueStraight() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (2, 80, 0, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.ContinueStraight, segments[0].ExitTransition); + Assert.True(segments[0].PreserveSprint); + } + + [Fact] + public void FromPath_AnnotatesOrthogonalTraverse_AsTurn() + { + var nodes = BuildNodes( + (0, 80, 0, MoveType.Traverse), + (1, 80, 0, MoveType.Traverse), + (1, 80, 1, MoveType.Traverse)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.Turn, segments[0].ExitTransition); + Assert.False(segments[0].PreserveSprint); + } + + [Fact] + public void FromPath_AnnotatesTraverseIntoParkour_AsPrepareJump() + { + var nodes = BuildNodes( + (120, 80, 110, MoveType.Traverse), + (121, 80, 110, MoveType.Traverse), + (123, 80, 110, MoveType.Parkour)); + + List segments = PathSegmentBuilder.FromPath(nodes); + + Assert.Equal(PathTransitionType.PrepareJump, segments[0].ExitTransition); + Assert.True(segments[0].PreserveSprint); + } + + private static List BuildNodes(params (int x, int y, int z, MoveType moveUsed)[] raw) + { + var result = new List(raw.Length); + for (int i = 0; i < raw.Length; i++) + { + var node = new PathNode(raw[i].x, raw[i].y, raw[i].z); + if (i > 0) + node.MoveUsed = raw[i].moveUsed; + result.Add(node); + } + return result; + } +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter PathSegmentBuilderTests -v minimal +``` + +Expected: FAIL because `PathSegmentBuilder` and `PathTransitionType` do not exist yet. + +- [ ] **Step 3: Add the transition enum and extend `PathSegment`** + +```csharp +// MinecraftClient/Pathing/Execution/PathTransitionType.cs +namespace MinecraftClient.Pathing.Execution +{ + public enum PathTransitionType + { + FinalStop, + ContinueStraight, + Turn, + PrepareJump, + LandingRecovery + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegment.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Execution +{ + public sealed class PathSegment + { + public required Location Start { get; init; } + public required Location End { get; init; } + public required MoveType MoveType { get; init; } + public PathTransitionType ExitTransition { get; init; } = PathTransitionType.FinalStop; + public bool PreserveSprint { get; init; } + + public int HeadingX => Math.Sign(End.X - Start.X); + public int HeadingZ => Math.Sign(End.Z - Start.Z); + + public override string ToString() => + $"{MoveType}: ({Start.X:F1},{Start.Y:F1},{Start.Z:F1})->({End.X:F1},{End.Y:F1},{End.Z:F1}), transition={ExitTransition}, preserveSprint={PreserveSprint}"; + } +} +``` + +- [ ] **Step 4: Add the builder and switch the manager to use it** + +```csharp +// MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs +using System; +using System.Collections.Generic; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Execution +{ + public static class PathSegmentBuilder + { + public static List FromPath(IReadOnlyList nodes) + { + var segments = new List(Math.Max(0, nodes.Count - 1)); + for (int i = 1; i < nodes.Count; i++) + { + PathSegment? next = null; + if (i + 1 < nodes.Count) + { + var nextNode = nodes[i + 1]; + var curr = nodes[i]; + next = new PathSegment + { + Start = new Location(curr.X + 0.5, curr.Y, curr.Z + 0.5), + End = new Location(nextNode.X + 0.5, nextNode.Y, nextNode.Z + 0.5), + MoveType = nextNode.MoveUsed + }; + } + + var prev = nodes[i - 1]; + var currNode = nodes[i]; + var current = new PathSegment + { + Start = new Location(prev.X + 0.5, prev.Y, prev.Z + 0.5), + End = new Location(currNode.X + 0.5, currNode.Y, currNode.Z + 0.5), + MoveType = currNode.MoveUsed + }; + + PathTransitionType exitTransition = Classify(current, next); + segments.Add(new PathSegment + { + Start = current.Start, + End = current.End, + MoveType = current.MoveType, + ExitTransition = exitTransition, + PreserveSprint = exitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump + }); + } + return segments; + } + + private static PathTransitionType Classify(PathSegment current, PathSegment? next) + { + if (next is null) + return PathTransitionType.FinalStop; + + if (next.MoveType is MoveType.Parkour or MoveType.Ascend) + return PathTransitionType.PrepareJump; + + if (current.MoveType is MoveType.Parkour or MoveType.Descend or MoveType.Fall) + return PathTransitionType.LandingRecovery; + + if (current.HeadingX == next.HeadingX && current.HeadingZ == next.HeadingZ) + return PathTransitionType.ContinueStraight; + + return PathTransitionType.Turn; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegmentManager.cs +public void StartNavigation(IGoal goal, PathResult result) +{ + _goal = goal; + _replanCount = 0; + var segments = PathSegmentBuilder.FromPath(result.Path); + _executor = new PathExecutor(segments, _debugLog); + _infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments"); +} + +private void Replan(Location pos, World world) +{ + // existing code omitted for brevity above + + var segments = PathSegmentBuilder.FromPath(result.Path); + _executor = new PathExecutor(segments, _debugLog); + _infoLog?.Invoke($"[PathMgr] Replanned: {segments.Count} segments (replan #{_replanCount})"); +} +``` + +- [ ] **Step 5: Run the tests and make sure the builder is green** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter PathSegmentBuilderTests -v minimal +``` + +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs \ + MinecraftClient/Pathing/Execution/PathTransitionType.cs \ + MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs \ + MinecraftClient/Pathing/Execution/PathSegment.cs \ + MinecraftClient/Pathing/Execution/PathSegmentManager.cs +git commit -m "feat: annotate path segments with transition intent" +``` + +### Task 3: Add the Predictive Braking Planner + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs` +- Create: `MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs` +- Create: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + +- [ ] **Step 1: Write failing deterministic planner tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs +using MinecraftClient.Mapping; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class FlatWorldTestBuilder +{ + public static World CreateStoneFloor(int floorY = 79, int min = -32, int max = 32) + { + World.LoadDefaultDimensions1206Plus(); + World.SetDimension("minecraft:overworld"); + + var world = new World(); + int minChunk = (int)Math.Floor(min / 16.0); + int maxChunk = (int)Math.Floor(max / 16.0); + + for (int chunkX = minChunk; chunkX <= maxChunk; chunkX++) + { + for (int chunkZ = minChunk; chunkZ <= maxChunk; chunkZ++) + { + world[chunkX, chunkZ] = new ChunkColumn(24) { FullyLoaded = true }; + } + } + + for (int x = min; x <= max; x++) + { + for (int z = min; z <= max; z++) + { + world.SetBlock(new Location(x, floorY, z), new Block(1)); + } + } + + return world; + } +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TransitionBrakingPlannerTests +{ + [Fact] + public void Plan_ReturnsCarryMomentum_ForContinueStraight() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.156, 0.0, onGround: true); + 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.ContinueStraight, + PreserveSprint = true + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.05, 80, 0.5), physics, world); + + Assert.True(decision.HoldForward); + Assert.True(decision.HoldSprint); + Assert.False(decision.HoldBack); + } + + [Fact] + public void Plan_ReleasesForward_ForFinalStop_WhenRemainingRunwayIsTooShort() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(); + var physics = CreatePhysics(0.156, 0.0, onGround: true); + 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.FinalStop, + PreserveSprint = false + }; + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(current, null, new Location(1.38, 80, 0.5), physics, world); + + Assert.False(decision.HoldForward); + Assert.False(decision.HoldSprint); + Assert.False(decision.HoldBack); + } + + [Fact] + public void ShouldReleaseForwardInAir_ReturnsTrue_ForParkourIntoTurn() + { + var physics = CreatePhysics(0.32, 0.0, onGround: false); + var current = new PathSegment + { + Start = new Location(120.5, 80, 110.5), + End = new Location(123.5, 80, 110.5), + MoveType = MoveType.Parkour, + ExitTransition = PathTransitionType.Turn, + PreserveSprint = false + }; + var next = new PathSegment + { + Start = new Location(123.5, 80, 110.5), + End = new Location(123.5, 80, 111.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + bool release = TransitionBrakingPlanner.ShouldReleaseForwardInAir(current, next, new Location(123.18, 80.92, 110.5), physics); + + Assert.True(release); + } + + private static PlayerPhysics CreatePhysics(double deltaX, double deltaZ, bool onGround) + { + return new PlayerPhysics + { + Position = new Vec3d(0.0, 80.0, 0.0), + DeltaMovement = new Vec3d(deltaX, 0.0, deltaZ), + OnGround = onGround, + MovementSpeed = 0.1f, + Yaw = 270f + }; + } +} +``` + +- [ ] **Step 2: Run the planner tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter TransitionBrakingPlannerTests -v minimal +``` + +Expected: FAIL because `TransitionBrakingPlanner` and `TransitionBrakingDecision` do not exist yet. + +- [ ] **Step 3: Add the decision type** + +```csharp +// MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs +namespace MinecraftClient.Pathing.Execution +{ + public readonly record struct TransitionBrakingDecision(bool HoldForward, bool HoldSprint, bool HoldBack) + { + public static TransitionBrakingDecision CarryMomentum(bool preserveSprint) => + new(true, preserveSprint, false); + + public static TransitionBrakingDecision Coast => + new(false, false, false); + + public static TransitionBrakingDecision Brake => + new(false, false, true); + } +} +``` + +- [ ] **Step 4: Add the braking planner** + +```csharp +// MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public static class TransitionBrakingPlanner + { + private const double GroundSpeedThreshold = 0.03; + private const int MaxSimulationTicks = 12; + private const double FinalStopLead = 0.04; + private const double TurnBrakeLead = 0.08; + private const double AirReleaseLead = 0.08; + + public static TransitionBrakingDecision Plan(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) + { + if (current.ExitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump) + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); + + double remaining = RemainingDistanceAlongSegment(current, pos); + double coastStopDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: false); + double hardBrakeDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: true); + + if (current.ExitTransition == PathTransitionType.Turn && remaining <= hardBrakeDistance + TurnBrakeLead) + return TransitionBrakingDecision.Brake; + + if (remaining <= coastStopDistance + FinalStopLead) + return TransitionBrakingDecision.Coast; + + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); + } + + public static bool ShouldReleaseForwardInAir(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics) + { + if (current.ExitTransition is not (PathTransitionType.FinalStop or PathTransitionType.Turn or PathTransitionType.LandingRecovery)) + return false; + + double remaining = RemainingDistanceAlongSegment(current, pos); + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ)); + + return remaining <= forwardSpeed + AirReleaseLead; + } + + public static double EstimateGroundStopDistance(PlayerPhysics physics, World world, int headingX, int headingZ, bool applyBackBrake) + { + if (!physics.OnGround) + return 0.0; + + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, headingX, headingZ)); + if (forwardSpeed <= GroundSpeedThreshold) + return 0.0; + + float blockFriction = PlayerPhysics.GetMaterialFriction( + world.GetBlock(new Location(physics.Position.X, physics.Position.Y - 0.5000010, physics.Position.Z)).Type); + double drag = blockFriction * PhysicsConsts.FrictionMultiplier; + double acceleration = physics.MovementSpeed + * (PhysicsConsts.GroundAccelerationFactor / (drag * drag * drag)) + * PhysicsConsts.InputFriction; + + if (applyBackBrake) + acceleration *= 0.98; + + double distance = 0.0; + double speed = forwardSpeed; + for (int tick = 0; tick < MaxSimulationTicks; tick++) + { + distance += speed; + speed = applyBackBrake + ? Math.Max(0.0, (speed - acceleration) * drag) + : speed * drag; + + if (speed <= GroundSpeedThreshold) + break; + } + + return distance; + } + + private static double RemainingDistanceAlongSegment(PathSegment current, Location pos) + { + double dx = current.End.X - pos.X; + double dz = current.End.Z - pos.Z; + return dx * current.HeadingX + dz * current.HeadingZ; + } + + private static double ProjectHorizontalSpeedAlongHeading(PlayerPhysics physics, int headingX, int headingZ) + { + return physics.DeltaMovement.X * headingX + physics.DeltaMovement.Z * headingZ; + } + } +} +``` + +- [ ] **Step 5: Run the tests and make sure the planner is green** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter TransitionBrakingPlannerTests -v minimal +``` + +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/FlatWorldTestBuilder.cs \ + MinecraftClient.Tests/Pathing/Execution/TransitionBrakingPlannerTests.cs \ + MinecraftClient/Pathing/Execution/TransitionBrakingDecision.cs \ + MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs +git commit -m "feat: add predictive transition braking planner" +``` + +### Task 4: Wire the Planner into the Templates and Executor + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs` +- Modify: `MinecraftClient/Pathing/Execution/IActionTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathExecutor.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs` + +- [ ] **Step 1: Write failing template-level tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Execution.Templates; +using MinecraftClient.Physics; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class TemplateBrakingTests +{ + [Fact] + public void WalkTemplate_CoastsInsteadOfHoldingForward_WhenFinalStopIsClose() + { + 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, + PreserveSprint = false + }; + + var template = new WalkTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.38, 80.0, 0.5), + DeltaMovement = new Vec3d(0.156, 0.0, 0.0), + OnGround = true, + Yaw = 270f + }; + var input = new MovementInput(); + + TemplateState state = template.Tick(new Location(1.38, 80, 0.5), physics, input, world); + + Assert.Equal(TemplateState.InProgress, state); + Assert.False(input.Forward); + Assert.False(input.Sprint); + Assert.False(input.Back); + } + + [Fact] + public void WalkTemplate_KeepsForward_WhenTransitionContinuesStraight() + { + 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.ContinueStraight, + PreserveSprint = true + }; + var next = new PathSegment + { + Start = new Location(1.5, 80, 0.5), + End = new Location(2.5, 80, 0.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new WalkTemplate(current, next); + var physics = new PlayerPhysics + { + Position = new Vec3d(1.10, 80.0, 0.5), + DeltaMovement = new Vec3d(0.140, 0.0, 0.0), + OnGround = true, + Yaw = 270f + }; + var input = new MovementInput(); + + TemplateState state = template.Tick(new Location(1.10, 80, 0.5), physics, input, world); + + Assert.Equal(TemplateState.InProgress, state); + Assert.True(input.Forward); + Assert.True(input.Sprint); + } +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter TemplateBrakingTests -v minimal +``` + +Expected: FAIL because templates do not accept `PathSegment`/`World` yet and do not consult the braking planner. + +- [ ] **Step 3: Change the executor and template plumbing to pass `World` and next-segment context** + +```csharp +// MinecraftClient/Pathing/Execution/IActionTemplate.cs +using MinecraftClient.Mapping; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution +{ + public interface IActionTemplate + { + Location ExpectedStart { get; } + Location ExpectedEnd { get; } + + TemplateState Tick(Location currentPos, PlayerPhysics physics, MovementInput input, World world); + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs +using System; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Execution.Templates; + +namespace MinecraftClient.Pathing.Execution +{ + public static class ActionTemplateFactory + { + public static IActionTemplate Create(PathSegment segment, PathSegment? nextSegment) + { + return segment.MoveType switch + { + MoveType.Traverse => new WalkTemplate(segment, nextSegment), + MoveType.Diagonal => new WalkTemplate(segment, nextSegment), + MoveType.Ascend => new AscendTemplate(segment, nextSegment), + MoveType.Descend => new DescendTemplate(segment, nextSegment), + MoveType.Fall => new FallTemplate(segment, nextSegment), + MoveType.Climb => new ClimbTemplate(segment, nextSegment), + MoveType.Parkour => new SprintJumpTemplate(segment, nextSegment), + _ => throw new ArgumentException($"Unknown MoveType: {segment.MoveType}") + }; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathExecutor.cs +public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + if (_currentTemplate is null) + { + input.Reset(); + return PathExecutorState.Complete; + } + + var state = _currentTemplate.Tick(pos, physics, input, world); + + switch (state) + { + case TemplateState.Complete: + input.Reset(); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})"); + _currentIndex++; + if (_currentIndex >= _segments.Count) + { + _currentTemplate = null; + _debugLog?.Invoke("[PathExec] All segments complete!"); + return PathExecutorState.Complete; + } + AdvanceToNextSegment(); + return PathExecutorState.InProgress; + + case TemplateState.Failed: + input.Reset(); + _debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " + + $"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " + + $"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})"); + return PathExecutorState.Failed; + + default: + return PathExecutorState.InProgress; + } +} + +private void AdvanceToNextSegment() +{ + if (_currentIndex < _segments.Count) + { + var seg = _segments[_currentIndex]; + PathSegment? next = _currentIndex + 1 < _segments.Count ? _segments[_currentIndex + 1] : null; + _currentTemplate = ActionTemplateFactory.Create(seg, next); + _debugLog?.Invoke($"[PathExec] Starting segment {_currentIndex}/{_segments.Count}: {seg}"); + } + else + { + _currentTemplate = null; + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegmentManager.cs +public void Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + if (_executor is null) + return; + + var state = _executor.Tick(pos, physics, input, world); + + switch (state) + { + case PathExecutorState.Complete: + _infoLog?.Invoke("[PathMgr] Navigation complete!"); + _executor = null; + _goal = null; + break; + + case PathExecutorState.Failed: + _infoLog?.Invoke("[PathMgr] Segment failed, replanning..."); + Replan(pos, world); + break; + } +} +``` + +- [ ] **Step 4: Wire the planner into the templates** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + internal static class TemplateHelper + { + // existing methods omitted + + internal static void ApplyDecision(MovementInput input, TransitionBrakingDecision decision) + { + input.Forward = decision.HoldForward; + input.Sprint = decision.HoldSprint; + input.Back = decision.HoldBack; + } + + internal static bool IsSettledAtEnd(Location pos, Location target, PlayerPhysics physics, double horizThresholdSq = 0.01, double speedThresholdSq = 0.0009) + { + double dx = target.X - pos.X; + double dz = target.Z - pos.Z; + double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X + + physics.DeltaMovement.Z * physics.DeltaMovement.Z; + return dx * dx + dz * dz <= horizThresholdSq && horizontalSpeedSq <= speedThresholdSq; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + public sealed class WalkTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private Location _lastPos; + private int _stuckTicks; + + public WalkTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + _lastPos = segment.Start; + } + + 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); + + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + + if (_segment.ExitTransition == PathTransitionType.ContinueStraight && TemplateHelper.IsNear(pos, ExpectedEnd, horizThresholdSq: 0.20)) + return TemplateState.Complete; + + if (_segment.ExitTransition != PathTransitionType.ContinueStraight && TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics)) + return TemplateState.Complete; + + double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos); + _stuckTicks = movedSq < 0.0005 ? _stuckTicks + 1 : 0; + _lastPos = pos; + + if (_stuckTicks > 40 || _tickCount > 100) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + public sealed class AscendTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private Location _lastPos; + private int _stuckTicks; + + public AscendTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + _lastPos = segment.Start; + } + + 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); + + input.Forward = true; + input.Sprint = true; + + if (physics.OnGround && dy > 0.1) + input.Jump = true; + + if (physics.OnGround && Math.Abs(dy) < 0.15) + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + if (_segment.ExitTransition != PathTransitionType.ContinueStraight && TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics, horizThresholdSq: 0.02)) + return TemplateState.Complete; + } + else if (dx * dx + dz * dz < 0.25 && Math.Abs(dy) < 0.8) + { + return TemplateState.Complete; + } + + double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos); + double movedY = Math.Abs(pos.Y - _lastPos.Y); + _stuckTicks = (movedSq < 0.0005 && movedY < 0.001) ? _stuckTicks + 1 : 0; + _lastPos = pos; + + if (_stuckTicks > 40 || _tickCount > 80) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + public sealed class DescendTemplate : IActionTemplate + { + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private int _tickCount; + private bool _hasFallen; + private readonly bool _needsSprint; + + public DescendTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + double hdx = segment.End.X - segment.Start.X; + double hdz = segment.End.Z - segment.Start.Z; + _needsSprint = (hdx * hdx + hdz * hdz) > 2.25; + } + + 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; + double horizDistSq = dx * dx + dz * dz; + + if (!physics.OnGround) + _hasFallen = true; + + if (_hasFallen && physics.OnGround) + { + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + + if (_segment.ExitTransition == PathTransitionType.ContinueStraight && horizDistSq < 0.5 && Math.Abs(dy) < 0.8) + return TemplateState.Complete; + + if (_segment.ExitTransition != PathTransitionType.ContinueStraight && TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics, horizThresholdSq: 0.02)) + return TemplateState.Complete; + } + else if (horizDistSq > 0.01) + { + 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); + input.Forward = true; + if (_needsSprint) + input.Sprint = true; + } + + if (pos.Y > ExpectedStart.Y + 2.0 || _tickCount > 200) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Physics; + +namespace MinecraftClient.Pathing.Execution.Templates +{ + public sealed class SprintJumpTemplate : IActionTemplate + { + private enum Phase { Approach, Airborne, Landing } + + public Location ExpectedStart { get; } + public Location ExpectedEnd { get; } + + private readonly PathSegment _segment; + private readonly PathSegment? _nextSegment; + private readonly double _horizDist; + private int _tickCount; + private Phase _phase = Phase.Approach; + private bool _leftGround; + + private const float YawToleranceDeg = 5f; + + public SprintJumpTemplate(PathSegment segment, PathSegment? nextSegment) + { + _segment = segment; + _nextSegment = nextSegment; + ExpectedStart = segment.Start; + ExpectedEnd = segment.End; + double dx = segment.End.X - segment.Start.X; + double dz = segment.End.Z - segment.Start.Z; + _horizDist = Math.Sqrt(dx * dx + dz * dz); + } + + 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; + double horizDistSq = dx * dx + dz * dz; + + 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.Approach: + input.Forward = true; + input.Sprint = true; + if (physics.OnGround) + { + double fromStartSq = TemplateHelper.HorizontalDistanceSq(pos, ExpectedStart); + float yawDelta = YawDifference(physics.Yaw, targetYaw); + double minApproachSq = _horizDist >= 4.0 ? 0.36 : _horizDist > 2.5 ? 0.09 : 0.0; + if (yawDelta < YawToleranceDeg && fromStartSq >= minApproachSq) + { + input.Jump = true; + _phase = Phase.Airborne; + } + } + break; + + case Phase.Airborne: + if (!physics.OnGround) + _leftGround = true; + + bool releaseInAir = TransitionBrakingPlanner.ShouldReleaseForwardInAir(_segment, _nextSegment, pos, physics); + if (releaseInAir || IsPastTarget(pos)) + { + 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: + TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world); + TemplateHelper.ApplyDecision(input, decision); + + if (_segment.ExitTransition == PathTransitionType.ContinueStraight && horizDistSq < 1.0 && Math.Abs(dy) < 1.0) + return TemplateState.Complete; + + if (_segment.ExitTransition != PathTransitionType.ContinueStraight && TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics, horizThresholdSq: 0.04)) + return TemplateState.Complete; + break; + } + + if (pos.Y < ExpectedEnd.Y - 4.0 || _tickCount > 60) + return TemplateState.Failed; + + return TemplateState.InProgress; + } + + private bool IsPastTarget(Location pos) + { + double dirX = ExpectedEnd.X - ExpectedStart.X; + double dirZ = ExpectedEnd.Z - ExpectedStart.Z; + double len = Math.Sqrt(dirX * dirX + dirZ * dirZ); + if (len < 0.001) return false; + dirX /= len; + dirZ /= len; + + double relX = pos.X - ExpectedEnd.X; + double relZ = pos.Z - ExpectedEnd.Z; + double dot = relX * dirX + relZ * dirZ; + return dot > 0.0; + } + + private static float YawDifference(float current, float target) + { + float delta = target - current; + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + return Math.Abs(delta); + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs and FallTemplate.cs +// Signature-only example to apply verbatim in both files: +public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + // existing body unchanged +} +``` + +- [ ] **Step 5: Run the test suite for the executor, planner, and templates** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "PathExecutorCompletionTests|PathSegmentBuilderTests|TransitionBrakingPlannerTests|TemplateBrakingTests" -v minimal +``` + +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/TemplateBrakingTests.cs \ + MinecraftClient/Pathing/Execution/IActionTemplate.cs \ + MinecraftClient/Pathing/Execution/ActionTemplateFactory.cs \ + MinecraftClient/Pathing/Execution/PathExecutor.cs \ + MinecraftClient/Pathing/Execution/PathSegmentManager.cs \ + MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/FallTemplate.cs +git commit -m "feat: wire transition braking into path templates" +``` + +### Task 5: Tune on a Real 1.21.11 Server and Document the Behavior + +**Files:** +- Create: `tools/test-transition-braking.sh` +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Write the failing 1.21.11 integration regression script** + +```bash +#!/usr/bin/env bash +# tools/test-transition-braking.sh +set -euo pipefail + +source "$(dirname "$0")/mcc-env.sh" + +VERSION="1.21.11-Vanilla" +SESSION="mcc-brake-test" +CFG="/tmp/mcc-debug/MinecraftClient.debug.ini" + +send_mcc() { + tmux send-keys -t "$SESSION" "$1" Enter +} + +capture_pane() { + tmux capture-pane -t "$SESSION" -p -S -120 +} + +extract_last_location() { + capture_pane | python3 - <<'PY' +import re +import sys + +text = sys.stdin.read() +matches = re.findall(r"Location\s+([-\d.]+),\s+([-\d.]+),\s+([-\d.]+)", text) +if not matches: + raise SystemExit("No Location line found in tmux capture") +x, y, z = matches[-1] +print(f"{x} {y} {z}") +PY +} + +assert_close() { + local actual_x="$1" + local actual_y="$2" + local actual_z="$3" + local expected_x="$4" + local expected_y="$5" + local expected_z="$6" + local tolerance="${7:-0.05}" + + python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" "$tolerance" +import math +import sys + +ax, ay, az, ex, ey, ez, tol = map(float, sys.argv[1:]) +if abs(ax - ex) > tol or abs(ay - ey) > tol or abs(az - ez) > tol: + raise SystemExit( + f"Expected ({ex:.2f}, {ey:.2f}, {ez:.2f}) within {tol:.2f}, got ({ax:.2f}, {ay:.2f}, {az:.2f})" + ) +PY +} + +source tools/mcc-env.sh +mcc-preflight "$VERSION" >/dev/null +mc-reset-test-env "$VERSION" >/dev/null +mc-start "$VERSION" >/dev/null + +if ! tmux has-session -t "$SESSION" 2>/dev/null; then + tmux new-session -d -s "$SESSION" -x 160 -y 50 \ + "cd '$MCC_REPO' && dotnet run --project MinecraftClient -c Release --no-build -- '$CFG' MCCBot - localhost:25565; echo '=== MCC EXITED ==='; sleep 600" + sleep 5 +fi + +mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true +send_mcc "/debug on" +sleep 1 + +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 +mc-rcon "tp MCCBot 100.5 80 100.5" >/dev/null +sleep 2 +send_mcc "/goto 103 80 100" +sleep 5 +send_mcc "/debug state" +sleep 1 +read -r x y z <<< "$(extract_last_location)" +assert_close "$x" "$y" "$z" "103.50" "80.00" "100.50" + +echo "== Parkour into turn ==" +mc-rcon "fill 118 79 108 126 79 112 air" >/dev/null +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 +mc-rcon "tp MCCBot 120.5 80 110.5" >/dev/null +sleep 2 +send_mcc "/goto 123 80 111" +sleep 6 +send_mcc "/debug state" +sleep 1 +read -r x y z <<< "$(extract_last_location)" +assert_close "$x" "$y" "$z" "123.50" "80.00" "111.50" + +echo "All transition braking checks passed." +``` + +- [ ] **Step 2: Run the real-server regression script and verify it fails before tuning** + +Run: + +```bash +chmod +x tools/test-transition-braking.sh +dotnet build MinecraftClient.sln -c Release +bash tools/test-transition-braking.sh +``` + +Expected: FAIL on at least one scenario because the initial planner constants will still be slightly loose on real 1.21.11 physics. + +- [ ] **Step 3: Tune the planner constants based on the live-server results** + +```csharp +// MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs +private const double GroundSpeedThreshold = 0.025; +private const int MaxSimulationTicks = 14; +private const double FinalStopLead = 0.06; +private const double TurnBrakeLead = 0.10; +private const double AirReleaseLead = 0.14; + +public static TransitionBrakingDecision Plan(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics, World world) +{ + if (current.ExitTransition is PathTransitionType.ContinueStraight or PathTransitionType.PrepareJump) + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); + + double remaining = RemainingDistanceAlongSegment(current, pos); + double coastStopDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: false); + double hardBrakeDistance = EstimateGroundStopDistance(physics, world, current.HeadingX, current.HeadingZ, applyBackBrake: true); + + if (current.ExitTransition == PathTransitionType.Turn && remaining <= hardBrakeDistance + TurnBrakeLead) + return TransitionBrakingDecision.Brake; + + if (remaining <= coastStopDistance + FinalStopLead) + return TransitionBrakingDecision.Coast; + + return TransitionBrakingDecision.CarryMomentum(current.PreserveSprint); +} + +public static bool ShouldReleaseForwardInAir(PathSegment current, PathSegment? next, Location pos, PlayerPhysics physics) +{ + if (current.ExitTransition is not (PathTransitionType.FinalStop or PathTransitionType.Turn or PathTransitionType.LandingRecovery)) + return false; + + double remaining = RemainingDistanceAlongSegment(current, pos); + double forwardSpeed = Math.Max(0.0, ProjectHorizontalSpeedAlongHeading(physics, current.HeadingX, current.HeadingZ)); + + return remaining <= forwardSpeed + AirReleaseLead; +} +``` + +- [ ] **Step 4: Re-run the local 1.21.11 regression script** + +Run: + +```bash +dotnet build MinecraftClient.sln -c Release +bash tools/test-transition-braking.sh +``` + +Expected: PASS with both scenarios landing within `0.05` blocks of the intended final center. + +- [ ] **Step 5: Update the pathfinding research doc** + +```md + +## Transition-Aware Braking + +MCC path execution now evaluates the next segment before finishing the current one. +The executor uses three exit styles: + +- `ContinueStraight`: finish early and preserve sprint so the next segment consumes the current velocity. +- `Turn` / `FinalStop`: release `Forward` early, then optionally tap `Back` on ground when the predicted stop distance is larger than the remaining runway. +- `PrepareJump` / `LandingRecovery`: preserve takeoff speed into jumps, but allow airborne forward release when the next segment is a turn or final stop. + +This deliberately differs from Baritone's default semantics. +Baritone treats many overshoots as success because the goal condition is usually "player feet entered the goal block". +MCC still uses block-goal semantics for path success, but the final segment controller now tries to settle near the target center on flat 1.21.11 terrain instead of accepting the old overshoot. +``` + +- [ ] **Step 6: Commit** + +```bash +git add tools/test-transition-braking.sh \ + MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs \ + docs/guide/pathfinding-research.md +git commit -m "feat: validate and document transition-aware braking" +``` + +## Self-Review + +### Spec coverage + +- Transition-aware braking based on the next segment: covered by Task 2 and Task 3. +- Clear stale input on segment completion: covered by Task 1. +- Airborne forward release before a turn or final stop: covered by Task 3 and Task 4. +- Walk / ascend / descend / parkour execution changes: covered by Task 4. +- Real 1.21.11 validation: covered by Task 5. +- Documentation update: covered by Task 5. + +### Placeholder scan + +- No `TODO`, `TBD`, “similar to Task N”, or “write tests for the above” placeholders remain. +- Every task includes exact file paths, exact commands, and code blocks for the specific change. + +### Type consistency + +- Transition enum name is `PathTransitionType` everywhere. +- Builder name is `PathSegmentBuilder` everywhere. +- Planner name is `TransitionBrakingPlanner` everywhere. +- Planner output type is `TransitionBrakingDecision` everywhere. diff --git a/docs/superpowers/plans/2026-04-13-pathing-contract-metrics-harness.md b/docs/superpowers/plans/2026-04-13-pathing-contract-metrics-harness.md new file mode 100644 index 0000000000..a279033c9f --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-pathing-contract-metrics-harness.md @@ -0,0 +1,1466 @@ +# Pathing Contract Metrics Harness 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:** Build a pathing test harness that treats planner failure or any replan as a hard failure, and reports per-route plus per-segment tick budgets so slow steps are visible immediately. + +**Architecture:** Split the problem into three layers. First, define scenario contracts that describe the expected planner output for each sterile route. Second, add execution telemetry that records actual segment durations and total route ticks without relying on fragile log scraping in unit tests. Third, reuse the same contracts and telemetry output in the live shell harness so unit tests and real-server runs speak the same language: planner contract, zero replan, total ticks, and which segment exceeded budget. + +**Tech Stack:** C# 14 / .NET 10, xUnit, MCC pathing execution stack (`PathSegmentManager`, `PathExecutor`, templates), JSON contract files under `MinecraftClient.Tests/TestData`, Bash + Python 3 live harness helpers under `tools/`, local `1.21.11-Vanilla` server via `tools/mcc-env.sh`. + +--- + +## Measurement Contract + +- Accepted deterministic routes fail immediately on any of: + - planner `Failed` + - planner `Partial` + - any executor `Replan #` + - any `[PathExec] Segment ... FAILED` + - total route ticks above budget + - any segment ticks above budget +- Rejected routes fail immediately on any of: + - planner `Success` or `Partial` + - navigation starting at all + - any replan attempt +- Timing uses two numbers per route and per segment: + - `expectedTicks`: best known sterile baseline + - `maxTicks`: enforced ceiling +- Initial `maxTicks` seeding rule: + - `maxTicks = expectedTicks + max(2, ceil(expectedTicks * 0.20))` +- After the first bootstrap pass, keep `expectedTicks` fixed in JSON and tune `maxTicks` only where the measured deterministic baseline proves the generic 20% rule is too loose or too tight. + +## File Structure + +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs` + - immutable scenario definition: id, world builder, start, goal, initial yaw, execution cap +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` + - sterile test worlds for short routes, jump combos, and long routes +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs` + - expected planner result and exact segment sequence +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingTimingBudget.cs` + - route and segment tick budgets +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs` + - JSON loader for planner contracts and timing budgets +- Create: `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` + - expected move sequence per scenario +- Create: `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` + - expected ticks and max ticks per route and per segment +- Create: `MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs` + - execution observer API +- Create: `MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs` + - machine-readable live telemetry lines for shell parsing +- Modify: `MinecraftClient/Pathing/Execution/PathExecutor.cs` + - emit segment start, complete, fail, and per-segment elapsed ticks +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` + - emit navigation start, complete, replan start, replan success, replan failure, and total route ticks +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs` + - in-memory capture of planner/executor events for assertions +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs` + - deterministic runner that plans, executes, and returns trace + logs +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs` + - compares actual plan/timing to JSON contracts and prints slow-step tables +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs` + - emits ready-to-paste JSON fragments from observed planner/timing traces +- Create: `MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs` + - explicit bootstrap tests used only to seed contracts +- Create: `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` + - exact planner contract assertions +- Create: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + - zero-replan and timing-budget assertions +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + - migrate existing manager smoke tests to the shared runner where useful +- Create: `tools/pathing_contract_report.py` + - shell-side parser for planner contracts, timing budgets, and MCC telemetry lines +- Modify: `tools/test-pathing-jump-combos.sh` + - call the report helper after each accepted route +- Modify: `tools/test-pathing-long-routes.sh` + - call the report helper after each accepted route +- Modify: `docs/guide/pathfinding-research.md` + - document planner vetoes, zero-replan rule, and timing metrics workflow + +### Task 1: Create Contract Types And Seed One Known Scenario + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingTimingBudget.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs` +- Create: `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- Create: `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` +- Test: `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` + +- [ ] **Step 1: Write the failing contract-loader test** + +```csharp +using MinecraftClient.Pathing.Core; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathPlanningContractTests +{ + [Fact] + public void Get_ManagerAcceptedAscendChain_LoadsExactPlannerContract() + { + PathingPlannerContract contract = PathingContractStore.GetPlanner("manager-accepted-ascend-chain"); + + Assert.Equal(PathStatus.Success, contract.ExpectedStatus); + Assert.Equal(6, contract.Segments.Length); + Assert.Collection(contract.Segments, + segment => + { + Assert.Equal(MoveType.Diagonal, segment.MoveType); + Assert.Equal(new PathingBlock(171, 80, 160), segment.StartBlock); + Assert.Equal(new PathingBlock(172, 80, 161), segment.EndBlock); + }, + segment => + { + Assert.Equal(MoveType.Ascend, segment.MoveType); + Assert.Equal(new PathingBlock(176, 82, 162), segment.StartBlock); + Assert.Equal(new PathingBlock(177, 83, 162), segment.EndBlock); + }); + } +} +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathPlanningContractTests.Get_ManagerAcceptedAscendChain_LoadsExactPlannerContract -v minimal` + +Expected: FAIL with a compile error because `PathingPlannerContract`, `PathingContractStore`, and `PathingBlock` do not exist yet. + +- [ ] **Step 3: Implement the contract models, store, and the first JSON entries** + +`MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs` + +```csharp +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathingBlock(int X, int Y, int Z); + +internal sealed record PathingPlannerSegmentContract +{ + public required MoveType MoveType { get; init; } + public required PathingBlock StartBlock { get; init; } + public required PathingBlock EndBlock { get; init; } +} + +internal sealed record PathingPlannerContract +{ + public required string ScenarioId { get; init; } + public required PathStatus ExpectedStatus { get; init; } + public required PathingPlannerSegmentContract[] Segments { get; init; } +} + +internal sealed record PathingSegmentTimingBudget +{ + public required MoveType MoveType { get; init; } + public required int ExpectedTicks { get; init; } + public required int MaxTicks { get; init; } +} + +internal sealed record PathingTimingBudget +{ + public required string ScenarioId { get; init; } + public required int ExpectedTotalTicks { get; init; } + public required int MaxTotalTicks { get; init; } + public required PathingSegmentTimingBudget[] Segments { get; init; } +} +``` + +`MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs` + +```csharp +using System.Text.Json; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingContractStore +{ + private static readonly Lazy> PlannerContracts = new(LoadPlanner); + private static readonly Lazy> TimingBudgets = new(LoadTiming); + + internal static PathingPlannerContract GetPlanner(string scenarioId) => PlannerContracts.Value[scenarioId]; + internal static PathingTimingBudget GetTiming(string scenarioId) => TimingBudgets.Value[scenarioId]; + + private static IReadOnlyDictionary LoadPlanner() => + Load("pathing-planner-contracts.json"); + + private static IReadOnlyDictionary LoadTiming() => + Load("pathing-timing-budgets.json"); + + private static IReadOnlyDictionary Load(string fileName) where TContract : class + { + string repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..")); + string path = Path.Combine(repoRoot, "MinecraftClient.Tests", "TestData", "Pathing", fileName); + + using FileStream stream = File.OpenRead(path); + var contracts = JsonSerializer.Deserialize(stream, new JsonSerializerOptions(JsonSerializerDefaults.Web)) + ?? throw new InvalidOperationException($"Failed to deserialize {path}"); + + return contracts.ToDictionary( + contract => (string)typeof(TContract).GetProperty("ScenarioId")!.GetValue(contract)!, + StringComparer.Ordinal); + } +} +``` + +`MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` + +```json +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedStatus": "Success", + "segments": [ + { "moveType": "Diagonal", "startBlock": { "x": 171, "y": 80, "z": 160 }, "endBlock": { "x": 172, "y": 80, "z": 161 } }, + { "moveType": "Diagonal", "startBlock": { "x": 172, "y": 80, "z": 161 }, "endBlock": { "x": 173, "y": 80, "z": 162 } }, + { "moveType": "Traverse", "startBlock": { "x": 173, "y": 80, "z": 162 }, "endBlock": { "x": 174, "y": 80, "z": 162 } }, + { "moveType": "Ascend", "startBlock": { "x": 174, "y": 80, "z": 162 }, "endBlock": { "x": 175, "y": 81, "z": 162 } }, + { "moveType": "Ascend", "startBlock": { "x": 175, "y": 81, "z": 162 }, "endBlock": { "x": 176, "y": 82, "z": 162 } }, + { "moveType": "Ascend", "startBlock": { "x": 176, "y": 82, "z": 162 }, "endBlock": { "x": 177, "y": 83, "z": 162 } } + ] + } +] +``` + +`MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` + +```json +[ + { + "scenarioId": "manager-accepted-ascend-chain", + "expectedTotalTicks": 0, + "maxTotalTicks": 0, + "segments": [ + { "moveType": "Diagonal", "expectedTicks": 0, "maxTicks": 0 }, + { "moveType": "Ascend", "expectedTicks": 0, "maxTicks": 0 } + ] + } +] +``` + +Notes: +- `pathing-timing-budgets.json` is intentionally seeded with zeroes only for the one scaffold scenario. Task 3 replaces them with measured values before timing assertions start. +- Keep planner contracts and timing budgets separate so segment sequence can be locked before budgets are calibrated. + +- [ ] **Step 4: Run the contract-loader test to verify it passes** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathPlanningContractTests.Get_ManagerAcceptedAscendChain_LoadsExactPlannerContract -v minimal` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/Contracts/PathingPlannerContract.cs \ + MinecraftClient.Tests/Pathing/Execution/Contracts/PathingContractStore.cs \ + MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json \ + MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json \ + MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs +git commit -m "test: add pathing contract store scaffold" +``` + +### Task 2: Add Execution Telemetry And A Deterministic Scenario Runner + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathExecutor.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + +- [ ] **Step 1: Write the failing runner test** + +```csharp +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class PathTimingContractTests +{ + [Fact] + public void Run_ManagerAcceptedAscendChain_CapturesPerSegmentTicks_AndZeroReplan() + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get("manager-accepted-ascend-chain"); + + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + Assert.Equal(0, result.ReplanCount); + Assert.True(result.Completed); + Assert.Equal(6, result.SegmentRuns.Count); + Assert.All(result.SegmentRuns, run => Assert.True(run.ElapsedTicks > 0)); + } +} +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathTimingContractTests.Run_ManagerAcceptedAscendChain_CapturesPerSegmentTicks_AndZeroReplan -v minimal` + +Expected: FAIL with missing type errors for `PathingExecutionScenarioCatalog`, `PathingScenarioRunner`, and `PathingScenarioResult`. + +- [ ] **Step 3: Implement the observer API, wire it into path execution, and add the first scenario runner** + +`MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs` + +```csharp +using MinecraftClient.Mapping; + +namespace MinecraftClient.Pathing.Execution.Telemetry; + +public interface IPathExecutionObserver +{ + void OnNavigationStarted(IReadOnlyList segments); + void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment); + void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position); + void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position); + void OnNavigationCompleted(int totalTicks); + void OnReplanStarted(int replanCount, Location position); + void OnReplanSucceeded(int replanCount, IReadOnlyList segments); + void OnReplanFailed(int replanCount, Location position); +} +``` + +`MinecraftClient/Pathing/Execution/PathExecutor.cs` + +```csharp +using MinecraftClient.Pathing.Execution.Telemetry; + +private readonly IPathExecutionObserver? _observer; +private int _segmentTicks; +private int _totalTicks; +public int TotalTicks => _totalTicks; + +public PathExecutor(List segments, Action? debugLog = null, IPathExecutionObserver? observer = null) +{ + _segments = segments; + _currentIndex = 0; + _debugLog = debugLog; + _observer = observer; + _observer?.OnNavigationStarted(segments); + AdvanceToNextSegment(); +} + +public PathExecutorState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + if (_currentTemplate is null) + { + input.Reset(); + return PathExecutorState.Complete; + } + + _segmentTicks++; + _totalTicks++; + var state = _currentTemplate.Tick(pos, physics, input, world); + + switch (state) + { + case TemplateState.Complete: + _observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); + input.Reset(); + _currentIndex++; + _segmentTicks = 0; + if (_currentIndex >= _segments.Count) + { + _currentTemplate = null; + return PathExecutorState.Complete; + } + AdvanceToNextSegment(); + return PathExecutorState.InProgress; + + case TemplateState.Failed: + _observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos); + input.Reset(); + return PathExecutorState.Failed; + + default: + return PathExecutorState.InProgress; + } +} + +private void AdvanceToNextSegment() +{ + if (_currentIndex < _segments.Count) + { + var seg = _segments[_currentIndex]; + PathSegment? next = _currentIndex + 1 < _segments.Count ? _segments[_currentIndex + 1] : null; + _currentTemplate = ActionTemplateFactory.Create(seg, next); + _observer?.OnSegmentStarted(_currentIndex, _segments.Count, seg); + } + else + { + _currentTemplate = null; + } +} +``` + +`MinecraftClient/Pathing/Execution/PathSegmentManager.cs` + +```csharp +using MinecraftClient.Pathing.Execution.Telemetry; + +private readonly IPathExecutionObserver? _observer; + +public PathSegmentManager(Action? debugLog = null, Action? infoLog = null, IPathExecutionObserver? observer = null) +{ + _debugLog = debugLog; + _infoLog = infoLog; + _observer = observer; +} + +public void StartNavigation(IGoal goal, PathResult result) +{ + _goal = goal; + _replanCount = 0; + var segments = PathSegmentBuilder.FromPath(result.Path); + _executor = new PathExecutor(segments, _debugLog, _observer); + _infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments"); +} + +public void Tick(Location pos, PlayerPhysics physics, MovementInput input, World world) +{ + if (_executor is null) + return; + + var state = _executor.Tick(pos, physics, input, world); + + switch (state) + { + case PathExecutorState.Complete: + _observer?.OnNavigationCompleted(_executor.TotalTicks); + _infoLog?.Invoke("[PathMgr] Navigation complete!"); + _executor = null; + _goal = null; + break; + + case PathExecutorState.Failed: + _infoLog?.Invoke("[PathMgr] Segment failed, replanning..."); + Replan(pos, world); + break; + } +} + +private void Replan(Location pos, World world) +{ + _replanCount++; + _observer?.OnReplanStarted(_replanCount, pos); + // existing logic... + if (result.Status == PathStatus.Failed || result.Path.Count < 2) + { + _observer?.OnReplanFailed(_replanCount, pos); + _executor = null; + _goal = null; + return; + } + + var segments = PathSegmentBuilder.FromPath(result.Path); + _observer?.OnReplanSucceeded(_replanCount, segments); + _executor = new PathExecutor(segments, _debugLog, _observer); +} +``` + +`MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs` + +```csharp +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathingExecutionScenario +{ + public required string Id { get; init; } + public required Func BuildWorld { get; init; } + public required Location Start { get; init; } + public required GoalBlock Goal { get; init; } + public required float StartYaw { get; init; } + public required int MaxExecutionTicks { get; init; } +} +``` + +`MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` + +```csharp +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingExecutionScenarioCatalog +{ + internal static PathingExecutionScenario Get(string scenarioId) => scenarioId switch + { + "manager-accepted-ascend-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildManagerAcceptedAscendChain, + Start = new Location(171.5, 80, 160.5), + Goal = new GoalBlock(177, 83, 162), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + _ => throw new ArgumentOutOfRangeException(nameof(scenarioId), scenarioId, null) + }; + + private static World BuildManagerAcceptedAscendChain() + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 158, max: 180); + FlatWorldTestBuilder.ClearBox(world, 170, 80, 160, 178, 85, 168); + FlatWorldTestBuilder.SetSolid(world, 175, 80, 162); + FlatWorldTestBuilder.SetSolid(world, 176, 81, 162); + FlatWorldTestBuilder.SetSolid(world, 177, 82, 162); + return world; + } +} +``` + +`MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs` + +```csharp +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution.Telemetry; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathSegmentRun(int SegmentIndex, MoveType MoveType, int ElapsedTicks, Location Position); + +internal sealed class RecordingPathExecutionObserver : IPathExecutionObserver +{ + internal List SegmentRuns { get; } = new(); + internal int ReplanCount { get; private set; } + internal int TotalTicks { get; private set; } + + public void OnNavigationStarted(IReadOnlyList segments) { } + public void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment) { } + + public void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => SegmentRuns.Add(new PathSegmentRun(segmentIndex, segment.MoveType, elapsedTicks, position)); + + public void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => SegmentRuns.Add(new PathSegmentRun(segmentIndex, segment.MoveType, elapsedTicks, position)); + + public void OnNavigationCompleted(int totalTicks) => TotalTicks = totalTicks; + public void OnReplanStarted(int replanCount, Location position) => ReplanCount = replanCount; + public void OnReplanSucceeded(int replanCount, IReadOnlyList segments) { } + public void OnReplanFailed(int replanCount, Location position) { } +} +``` + +`MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs` + +```csharp +using System.Threading; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Physics; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal sealed record PathingScenarioResult( + bool Completed, + int ReplanCount, + int TotalTicks, + IReadOnlyList SegmentRuns, + IReadOnlyList DebugLogs, + IReadOnlyList InfoLogs, + Location FinalPosition, + PathResult PlanResult); + +internal static class PathingScenarioRunner +{ + internal static PathResult PlanOnly(PathingExecutionScenario scenario) + { + World world = scenario.BuildWorld(); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var finder = new AStarPathFinder(); + + return finder.Calculate( + ctx, + (int)Math.Floor(scenario.Start.X), + (int)Math.Floor(scenario.Start.Y), + (int)Math.Floor(scenario.Start.Z), + scenario.Goal, + CancellationToken.None, + timeoutMs: 3000); + } + + internal static PathingScenarioResult RunAccepted(PathingExecutionScenario scenario) + { + World world = scenario.BuildWorld(); + var debugLogs = new List(); + var infoLogs = new List(); + var observer = new RecordingPathExecutionObserver(); + var manager = new PathSegmentManager(debugLogs.Add, infoLogs.Add, observer); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(scenario.Start, scenario.StartYaw); + var input = new MovementInput(); + + PathResult planResult = PlanOnly(scenario); + + manager.StartNavigation(scenario.Goal, planResult); + + int tick = 0; + for (; tick < scenario.MaxExecutionTicks && 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); + } + + return new PathingScenarioResult( + Completed: !manager.IsNavigating, + ReplanCount: manager.ReplanCount, + TotalTicks: tick, + SegmentRuns: observer.SegmentRuns, + DebugLogs: debugLogs, + InfoLogs: infoLogs, + FinalPosition: new Location(physics.Position.X, physics.Position.Y, physics.Position.Z), + PlanResult: planResult); + } +} +``` + +- [ ] **Step 4: Run the runner test to verify it passes** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathTimingContractTests.Run_ManagerAcceptedAscendChain_CapturesPerSegmentTicks_AndZeroReplan -v minimal` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient/Pathing/Execution/Telemetry/IPathExecutionObserver.cs \ + MinecraftClient/Pathing/Execution/PathExecutor.cs \ + MinecraftClient/Pathing/Execution/PathSegmentManager.cs \ + MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenario.cs \ + MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs \ + MinecraftClient.Tests/Pathing/Execution/Support/RecordingPathExecutionObserver.cs \ + MinecraftClient.Tests/Pathing/Execution/Support/PathingScenarioRunner.cs \ + MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs +git commit -m "test: record path execution segment timings" +``` + +### Task 3: Lock Planner Vetoes And Short-Route Timing Budgets + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + +- [ ] **Step 1: Write bootstrap tests that print planner and timing JSON fragments for the known short routes** + +```csharp +using Xunit.Abstractions; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static PathingExecutionScenario Get(string scenarioId) => scenarioId switch +{ + "same-move-ascend-staircase" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveAscendStaircase, + Start = new Location(340.5, 80, 340.5), + Goal = new GoalBlock(345, 85, 340), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "same-move-descend-staircase" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildSameMoveDescendStaircase, + Start = new Location(362.5, 85, 360.5), + Goal = new GoalBlock(367, 80, 360), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "rejected-3x1-invalid-goal" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRejectedThreeByOneInvalidGoal, + Start = new Location(141.5, 80, 138.5), + Goal = new GoalBlock(144, 81, 138), + StartYaw = 270f, + MaxExecutionTicks = 80 + }, + _ => throw new ArgumentOutOfRangeException(nameof(scenarioId), scenarioId, null) +}; + +public sealed class PathingContractBootstrapTests +{ + private readonly ITestOutputHelper _output; + + public PathingContractBootstrapTests(ITestOutputHelper output) => _output = output; + + [Theory] + [InlineData("same-move-ascend-staircase")] + [InlineData("same-move-descend-staircase")] + [InlineData("rejected-3x1-invalid-goal")] + public void PrintShortRouteContractFragments(string scenarioId) + { + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + if (planResult.Status == PathStatus.Success) + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); + } +} +``` + +- [ ] **Step 2: Run the bootstrap tests and capture the JSON fragments** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathingContractBootstrapTests.PrintShortRouteContractFragments -v minimal` + +Expected: +- PASS +- output contains ready-to-paste JSON for: + - `same-move-ascend-staircase` + - `same-move-descend-staircase` + - `rejected-3x1-invalid-goal` + +- [ ] **Step 3: Paste the emitted fragments into the contract files, then write the enforcing assertions** + +`MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs` + +```csharp +using System.Text; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Execution; +using MinecraftClient.Pathing.Core; +using Xunit; +using Xunit.Sdk; + +namespace MinecraftClient.Tests.Pathing.Execution; + +internal static class PathingContractAssert +{ + internal static void PlannerMatches(PathingPlannerContract contract, IReadOnlyList segments, PathResult result) + { + if (result.Status != contract.ExpectedStatus) + throw new XunitException($"planner status mismatch: expected {contract.ExpectedStatus}, got {result.Status}"); + + if (segments.Count != contract.Segments.Length) + throw new XunitException($"segment count mismatch: expected {contract.Segments.Length}, got {segments.Count}"); + + for (int i = 0; i < segments.Count; i++) + { + PathSegment actual = segments[i]; + PathingPlannerSegmentContract expected = contract.Segments[i]; + + Assert.Equal(expected.MoveType, actual.MoveType); + Assert.Equal(expected.StartBlock, ToBlock(actual.Start)); + Assert.Equal(expected.EndBlock, ToBlock(actual.End)); + } + } + + internal static void TimingMatches(PathingTimingBudget budget, PathingScenarioResult result) + { + if (!result.Completed) + throw new XunitException("navigation did not complete"); + if (result.ReplanCount != 0) + throw new XunitException($"expected 0 replans, saw {result.ReplanCount}\n{Format(result, budget)}"); + if (result.TotalTicks > budget.MaxTotalTicks) + throw new XunitException($"route exceeded budget: actual={result.TotalTicks} max={budget.MaxTotalTicks}\n{Format(result, budget)}"); + if (result.SegmentRuns.Count != budget.Segments.Length) + throw new XunitException($"segment timing count mismatch: actual={result.SegmentRuns.Count} expected={budget.Segments.Length}"); + + for (int i = 0; i < budget.Segments.Length; i++) + { + if (result.SegmentRuns[i].ElapsedTicks > budget.Segments[i].MaxTicks) + throw new XunitException($"segment {i} exceeded budget\n{Format(result, budget)}"); + } + } + + private static string Format(PathingScenarioResult result, PathingTimingBudget budget) + { + var sb = new StringBuilder(); + sb.AppendLine($"route actual={result.TotalTicks} expected={budget.ExpectedTotalTicks} max={budget.MaxTotalTicks}"); + for (int i = 0; i < result.SegmentRuns.Count; i++) + { + var actual = result.SegmentRuns[i]; + var expected = budget.Segments[i]; + sb.AppendLine($"seg[{i}] move={actual.MoveType} actual={actual.ElapsedTicks} expected={expected.ExpectedTicks} max={expected.MaxTicks}"); + } + return sb.ToString(); + } + + private static PathingBlock ToBlock(Location location) => + new((int)Math.Floor(location.X), (int)Math.Floor(location.Y), (int)Math.Floor(location.Z)); +} +``` + +`MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` + +```csharp +[Theory] +[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.GetPlanner(scenarioId); + + PathingContractAssert.PlannerMatches(contract, PathSegmentBuilder.FromPath(planResult.Path), planResult); +} +``` + +`MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + +```csharp +[Theory] +[InlineData("same-move-ascend-staircase")] +[InlineData("same-move-descend-staircase")] +public void Scenario_ExecutionStaysWithinTimingBudget(string scenarioId) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); +} +``` + +- [ ] **Step 4: Run the short-route planner and timing tests** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.PathPlanningContractTests|FullyQualifiedName~Pathing.Execution.PathTimingContractTests" -v minimal` + +Expected: +- planner tests PASS for the two accepted short routes and the direct planner rejection +- timing tests PASS for ascend and descend staircase + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/Support/PathingContractAssert.cs \ + MinecraftClient.Tests/Pathing/Execution/Support/PathingContractBootstrapWriter.cs \ + MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs \ + MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs \ + MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json \ + MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json \ + MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs +git commit -m "test: lock short route planner and timing contracts" +``` + +### Task 4: Bootstrap And Lock Complex Jump-Combo Contracts + +**Files:** +- Modify: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + +- [ ] **Step 1: Add the jump-combo sterile worlds and bootstrap coverage** + +`MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` + +```csharp +internal static PathingExecutionScenario Get(string scenarioId) => scenarioId switch +{ + "repeated-cardinal-parkour-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRepeatedCardinalParkourChain, + Start = new Location(580.5, 80, 580.5), + Goal = new GoalBlock(588, 80, 580), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "repeated-diagonal-parkour-chain" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildRepeatedDiagonalParkourChain, + Start = new Location(600.5, 80, 600.5), + Goal = new GoalBlock(606, 80, 606), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + "obstructed-parkour-l-turns" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildObstructedParkourLTurns, + Start = new Location(620.5, 80, 620.5), + Goal = new GoalBlock(626, 80, 622), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "vertical-jump-mix" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildVerticalJumpMix, + Start = new Location(640.5, 80, 620.5), + Goal = new GoalBlock(648, 80, 620), + StartYaw = 270f, + MaxExecutionTicks = 420 + }, + "diagonal-vertical-mix" => new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = BuildDiagonalVerticalMix, + Start = new Location(680.5, 80, 620.5), + Goal = new GoalBlock(684, 80, 624), + StartYaw = 315f, + MaxExecutionTicks = 420 + }, + _ => throw new ArgumentOutOfRangeException(nameof(scenarioId), scenarioId, null) +}; +``` + +Add world builders that mirror the live harness coordinates exactly: + +```csharp +private static World BuildRepeatedCardinalParkourChain() +{ + 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); + return world; +} + +private static World BuildRepeatedDiagonalParkourChain() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 598, max: 608); + FlatWorldTestBuilder.ClearBox(world, 598, 79, 598, 608, 90, 608); + FlatWorldTestBuilder.SetSolid(world, 600, 79, 600); + FlatWorldTestBuilder.SetSolid(world, 602, 79, 602); + FlatWorldTestBuilder.SetSolid(world, 604, 79, 604); + FlatWorldTestBuilder.SetSolid(world, 606, 79, 606); + return world; +} +``` + +Also port the exact geometry from: +- `tools/test-pathing-jump-combos.sh:264` +- `tools/test-pathing-jump-combos.sh:275` +- `tools/test-pathing-jump-combos.sh:285` + +Update bootstrap coverage: + +```csharp +[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 PrintJumpComboContractFragments(string scenarioId) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); +} +``` + +- [ ] **Step 2: Run the bootstrap tests and paste the emitted planner contracts and timing budgets** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathingContractBootstrapTests.PrintJumpComboContractFragments -v minimal` + +Expected: +- PASS +- output contains planner JSON and timing JSON for all five jump-combo scenarios + +- [ ] **Step 3: Write enforcing theories for the jump-combo planner and timing contracts** + +```csharp +[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) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + PathingContractAssert.PlannerMatches( + PathingContractStore.GetPlanner(scenarioId), + 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_ExecutionStaysWithinBudget(string scenarioId) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); +} +``` + +- [ ] **Step 4: Run the jump-combo contract suite** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution.JumpCombo_" -v minimal` + +Expected: +- currently this suite may FAIL before later implementation work +- failure output must now show exactly which segment exceeded budget or triggered replan + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs \ + MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json \ + MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json \ + MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs +git commit -m "test: add jump combo planner and timing contracts" +``` + +### Task 5: Bootstrap And Lock Long-Route Contracts + +**Files:** +- Modify: `MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- Modify: `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs` + +- [ ] **Step 1: Port the long-route geometry and bootstrap the planner/timing JSON** + +Add scenario ids that mirror the shell suite exactly: + +```csharp +"same-move-straight-traverse-chain", +"same-move-diagonal-chain", +"same-move-ascend-staircase", +"same-move-descend-staircase", +"same-move-aligned-parkour-chain", +"mixed-traverse-turn-parkour-turn-traverse", +"mixed-diagonal-ascend-traverse-descend", +"mixed-traverse-ascend-parkour-descend", +"turn-density-alternating-traverse-diagonal-chain", +"speed-carry-repeated-traverse-ascend", +"speed-carry-repeated-traverse-descend", +"speed-carry-repeated-traverse-parkour" +``` + +Mirror the shell layouts from: +- `tools/test-pathing-long-routes.sh:64` +- `tools/test-pathing-long-routes.sh:88` +- `tools/test-pathing-long-routes.sh:134` +- `tools/test-pathing-long-routes.sh:171` + +Update bootstrap coverage: + +```csharp +[Theory] +[InlineData("same-move-straight-traverse-chain")] +[InlineData("same-move-diagonal-chain")] +[InlineData("same-move-ascend-staircase")] +[InlineData("same-move-descend-staircase")] +[InlineData("same-move-aligned-parkour-chain")] +[InlineData("mixed-traverse-turn-parkour-turn-traverse")] +[InlineData("mixed-diagonal-ascend-traverse-descend")] +[InlineData("mixed-traverse-ascend-parkour-descend")] +[InlineData("turn-density-alternating-traverse-diagonal-chain")] +[InlineData("speed-carry-repeated-traverse-ascend")] +[InlineData("speed-carry-repeated-traverse-descend")] +[InlineData("speed-carry-repeated-traverse-parkour")] +public void PrintLongRouteContractFragments(string scenarioId) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathResult planResult = PathingScenarioRunner.PlanOnly(scenario); + + _output.WriteLine(PathingContractBootstrapWriter.WritePlannerFragment(scenarioId, planResult)); + _output.WriteLine(PathingContractBootstrapWriter.WriteTimingFragment(scenarioId, PathingScenarioRunner.RunAccepted(scenario))); +} +``` + +- [ ] **Step 2: Run the bootstrap tests and paste the long-route JSON** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.PathingContractBootstrapTests.PrintLongRouteContractFragments -v minimal` + +Expected: +- PASS +- output contains planner and timing fragments for all long-route scenarios + +- [ ] **Step 3: Add enforcing theories for the long-route contracts** + +```csharp +[Theory] +[InlineData("same-move-straight-traverse-chain")] +[InlineData("same-move-diagonal-chain")] +[InlineData("same-move-ascend-staircase")] +[InlineData("same-move-descend-staircase")] +[InlineData("same-move-aligned-parkour-chain")] +[InlineData("mixed-traverse-turn-parkour-turn-traverse")] +[InlineData("mixed-diagonal-ascend-traverse-descend")] +[InlineData("mixed-traverse-ascend-parkour-descend")] +[InlineData("turn-density-alternating-traverse-diagonal-chain")] +[InlineData("speed-carry-repeated-traverse-ascend")] +[InlineData("speed-carry-repeated-traverse-descend")] +[InlineData("speed-carry-repeated-traverse-parkour")] +public void LongRoute_ExecutionStaysWithinBudget(string scenarioId) +{ + PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId); + PathingTimingBudget budget = PathingContractStore.GetTiming(scenarioId); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + PathingContractAssert.TimingMatches(budget, result); +} +``` + +- [ ] **Step 4: Run the long-route timing suite** + +Run: `dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~Pathing.Execution.LongRoute_ -v minimal` + +Expected: +- initially some cases may FAIL +- every failure message must identify the route total and the exact slow segment index/move + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/Scenarios/PathingExecutionScenarioCatalog.cs \ + MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json \ + MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json \ + MinecraftClient.Tests/Pathing/Execution/PathingContractBootstrapTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathTimingContractTests.cs +git commit -m "test: add long route timing contracts" +``` + +### Task 6: Expose The Same Metrics In The Live Harness + +**Files:** +- Create: `MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs` +- Modify: `MinecraftClient/McClient.cs` +- Modify: `MinecraftClient/Resources/Translations/Translations.resx` +- Modify: `MinecraftClient/Resources/Translations/Translations.Designer.cs` +- Create: `tools/pathing_contract_report.py` +- Create: `tools/tests/test_pathing_contract_report.py` +- Modify: `tools/test-pathing-jump-combos.sh` +- Modify: `tools/test-pathing-long-routes.sh` + +- [ ] **Step 1: Write the failing live-report parser test against a saved log slice** + +Create a tiny fixture log under `tools/testdata/pathing-contract-report.sample.log` and a Python test: + +```python +from pathing_contract_report import parse_metrics + +def test_parse_metrics_reads_route_and_segment_ticks(tmp_path): + log = tmp_path / "sample.log" + log.write_text( + "[PathMetric] routeStart segments=4\n" + "[PathMetric] segmentComplete index=0 move=Parkour ticks=17\n" + "[PathMetric] segmentComplete index=1 move=Parkour ticks=16\n" + "[PathMetric] routeComplete totalTicks=70 replans=0\n", + encoding="utf-8", + ) + + report = parse_metrics(log.read_text(encoding="utf-8")) + + assert report.total_ticks == 70 + assert [segment.ticks for segment in report.segments] == [17, 16] +``` + +- [ ] **Step 2: Run the parser test to verify it fails** + +Run: `python3 -m pytest tools/tests/test_pathing_contract_report.py -q` + +Expected: FAIL because `pathing_contract_report.py` does not exist yet. + +- [ ] **Step 3: Implement a machine-readable log observer and the shell report helper** + +`MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs` + +```csharp +using MinecraftClient.Mapping; +using MinecraftClient.Resources; + +namespace MinecraftClient.Pathing.Execution.Telemetry; + +public sealed class PathExecutionLogObserver : IPathExecutionObserver +{ + private readonly Action? _debug; + private int _routeTicks; + + public PathExecutionLogObserver(Action? debug) => _debug = debug; + + public void OnNavigationStarted(IReadOnlyList segments) + => _debug?.Invoke(string.Format(Translations.pathing_metric_route_start, segments.Count)); + + public void OnSegmentStarted(int segmentIndex, int totalSegments, PathSegment segment) + => _debug?.Invoke(string.Format(Translations.pathing_metric_segment_start, + segmentIndex, totalSegments, segment.MoveType, segment.ExitTransition)); + + public void OnSegmentCompleted(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => _debug?.Invoke(string.Format(Translations.pathing_metric_segment_complete, + segmentIndex, totalSegments, segment.MoveType, elapsedTicks, position.X, position.Y, position.Z)); + + public void OnSegmentFailed(int segmentIndex, int totalSegments, PathSegment segment, int elapsedTicks, Location position) + => _debug?.Invoke(string.Format(Translations.pathing_metric_segment_failed, + segmentIndex, totalSegments, segment.MoveType, elapsedTicks, position.X, position.Y, position.Z)); + + public void OnNavigationCompleted(int totalTicks) + => _debug?.Invoke(string.Format(Translations.pathing_metric_route_complete, totalTicks)); + + public void OnReplanStarted(int replanCount, Location position) + => _debug?.Invoke(string.Format(Translations.pathing_metric_replan_start, + replanCount, position.X, position.Y, position.Z)); + + public void OnReplanSucceeded(int replanCount, IReadOnlyList segments) + => _debug?.Invoke(string.Format(Translations.pathing_metric_replan_success, replanCount, segments.Count)); + + public void OnReplanFailed(int replanCount, Location position) + => _debug?.Invoke(string.Format(Translations.pathing_metric_replan_failed, + replanCount, position.X, position.Y, position.Z)); +} +``` + +Add the matching translation entries so the new user-visible log lines stay inside the localization system: + +```xml +[PathMetric] routeStart segments={0} +[PathMetric] segmentStart index={0} total={1} move={2} transition={3} +[PathMetric] segmentComplete index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2} +[PathMetric] segmentFailed index={0} total={1} move={2} ticks={3} x={4:F2} y={5:F2} z={6:F2} +[PathMetric] routeComplete totalTicks={0} +[PathMetric] replanStart count={0} x={1:F2} y={2:F2} z={3:F2} +[PathMetric] replanSuccess count={0} segments={1} +[PathMetric] replanFailed count={0} x={1:F2} y={2:F2} z={3:F2} +``` + +`MinecraftClient/McClient.cs` + +```csharp +pathSegmentManager = new Pathing.Execution.PathSegmentManager( + debugLog: msg => Log.Debug(msg), + infoLog: msg => Log.Info(msg), + observer: new Pathing.Execution.Telemetry.PathExecutionLogObserver(msg => Log.Debug(msg))); +``` + +`tools/pathing_contract_report.py` + +```python +from __future__ import annotations + +import json +import math +import pathlib +import re +from dataclasses import dataclass + +SEGMENT_RE = re.compile(r"\[PathMetric\] segmentComplete index=(?P\d+) total=(?P\d+) move=(?P\w+) ticks=(?P\d+)") +ROUTE_RE = re.compile(r"\[PathMetric\] routeComplete totalTicks=(?P\d+)") +PLAN_RE = re.compile(r"\[Navigate\]\s+seg\[(?P\d+)\] = (?P\w+): \((?P-?\d+),(?P-?\d+),(?P-?\d+)\)") + +@dataclass +class SegmentMetric: + index: int + move: str + ticks: int + +def load_json(path: pathlib.Path): + return {entry["scenarioId"]: entry for entry in json.loads(path.read_text(encoding="utf-8"))} + +def parse_metrics(text: str): + segments = [SegmentMetric(int(m["index"]), m["move"], int(m["ticks"])) for m in SEGMENT_RE.finditer(text)] + route_match = ROUTE_RE.search(text) + total_ticks = int(route_match["ticks"]) if route_match else None + planned = [(int(m["index"]), m["move"], (int(m["x"]), int(m["y"]), int(m["z"]))) for m in PLAN_RE.finditer(text)] + return {"segments": segments, "totalTicks": total_ticks, "planned": planned} + +def main() -> int: + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--scenario-id", required=True) + parser.add_argument("--log-file", required=True) + parser.add_argument("--from-line", type=int, required=True) + parser.add_argument("--planner-contracts", required=True) + parser.add_argument("--timing-budgets", required=True) + args = parser.parse_args() + + log_path = pathlib.Path(args.log_file) + text = "\n".join(log_path.read_text(encoding="utf-8", errors="ignore").splitlines()[args.from_line:]) + planner = load_json(pathlib.Path(args.planner_contracts))[args.scenario_id] + timing = load_json(pathlib.Path(args.timing_budgets))[args.scenario_id] + actual = parse_metrics(text) + + if actual["totalTicks"] is None: + raise SystemExit("Missing [PathMetric] routeComplete line") + if actual["totalTicks"] > timing["maxTotalTicks"]: + raise SystemExit(f"Route exceeded budget: actual={actual['totalTicks']} max={timing['maxTotalTicks']}") + + for expected, actual_segment in zip(timing["segments"], actual["segments"], strict=True): + if actual_segment.ticks > expected["maxTicks"]: + raise SystemExit( + f"Segment {actual_segment.index} slow: move={actual_segment.move} actual={actual_segment.ticks} max={expected['maxTicks']}" + ) + + print(f"Route {args.scenario_id}: actual={actual['totalTicks']} expected={timing['expectedTotalTicks']} max={timing['maxTotalTicks']}") + for expected, actual_segment in zip(timing["segments"], actual["segments"], strict=True): + delta = actual_segment.ticks - expected["expectedTicks"] + print( + f" seg[{actual_segment.index}] move={actual_segment.move} actual={actual_segment.ticks} " + f"expected={expected['expectedTicks']} max={expected['maxTicks']} delta={delta:+d}" + ) + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) +``` + +- [ ] **Step 4: Integrate the report helper into both live scripts** + +Add to `tools/test-pathing-jump-combos.sh` and `tools/test-pathing-long-routes.sh` inside `run_accepted_route()` after `assert_no_replans_since`: + +```bash +python3 "$REPO_ROOT/tools/pathing_contract_report.py" \ + --scenario-id "$scenario_id" \ + --log-file "$LOG" \ + --from-line "$start_line" \ + --planner-contracts "$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json" \ + --timing-budgets "$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json" +``` + +Change the route helper signature so each case passes both a display label and the stable contract id: + +```bash +run_accepted_route() { + local scenario_id="$1" + local label="$2" + local start_x="$3" + local start_y="$4" + local start_z="$5" + local goal_x="$6" + local goal_y="$7" + local goal_z="$8" + local timeout="${9:-45}" + # existing body... +} +``` + +- [ ] **Step 5: Run the parser tests and both live suites** + +Run: + +```bash +python3 -m pytest tools/tests/test_pathing_contract_report.py -q +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: +- parser test PASS +- live harness now prints route totals and per-segment slow-step tables +- accepted routes fail on planner mismatch, replan, or budget overrun + +- [ ] **Step 6: Commit** + +```bash +git add MinecraftClient/Pathing/Execution/Telemetry/PathExecutionLogObserver.cs \ + MinecraftClient/McClient.cs \ + MinecraftClient/Resources/Translations/Translations.resx \ + MinecraftClient/Resources/Translations/Translations.Designer.cs \ + tools/pathing_contract_report.py \ + tools/test-pathing-jump-combos.sh \ + tools/test-pathing-long-routes.sh +git commit -m "test: surface path timing contracts in live harness" +``` + +### Task 7: Document The Workflow And Final Verification + +**Files:** +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Add a short contract section to the docs** + +```md +### Deterministic pathing contract + +Accepted sterile routes must satisfy all of the following: + +- planner result is `Success` +- planner result is not `Partial` +- navigation completes with `0 replan` +- every executed segment stays within its checked-in tick budget +- total route ticks stay within the checked-in route budget + +Rejected routes must fail during planning and must never start navigation. + +The authoritative contract files are: + +- `MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json` +- `MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json` +``` + +- [ ] **Step 2: Run the full focused verification set** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal +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: +- xUnit pathing suite passes for the currently fixed scenarios +- live suites print per-route and per-segment timing metrics +- any remaining pathing regressions now fail with explicit slow-step or replan diagnostics + +- [ ] **Step 3: Commit** + +```bash +git add docs/guide/pathfinding-research.md +git commit -m "docs: document pathing contract metrics workflow" +``` + +## Self-Review + +- Spec coverage: + - planner failure and partial path are hard vetoes: covered in Tasks 3, 4, 5, and 6 + - zero replan for sterile accepted routes: covered in Tasks 2, 3, 4, 5, and 6 + - total route timing constraint: covered in Tasks 3, 4, 5, and 6 + - per-step timing visibility: covered in `PathingContractAssert` and `tools/pathing_contract_report.py` + - ability to identify which action is slow: covered by segment-level contract assertions and live-shell report output +- Placeholder scan: + - no `TODO`, `TBD`, or “similar to task N” references remain + - the only “measure then paste” flow is explicit bootstrap output, not an unspecified placeholder +- Type consistency: + - shared names are fixed across tasks: `PathingExecutionScenario`, `PathingScenarioResult`, `PathingPlannerContract`, `PathingTimingBudget`, `PathingContractStore`, `PathingContractAssert` + +Plan complete and saved to `docs/superpowers/plans/2026-04-13-pathing-contract-metrics-harness.md`. Two execution options: + +**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration + +**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints + +**Which approach?** diff --git a/docs/superpowers/plans/2026-04-13-zero-replan-live-pathing.md b/docs/superpowers/plans/2026-04-13-zero-replan-live-pathing.md new file mode 100644 index 0000000000..26ef34368c --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-zero-replan-live-pathing.md @@ -0,0 +1,736 @@ +# Zero Replan Live Pathing 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:** Eliminate executor-driven replans in deterministic live harness scenarios on `1.21.11-Vanilla`, and add longer-route stability coverage that proves accepted routes finish with `0 replan`. + +**Architecture:** Treat any `replan` in accepted deterministic harness scenarios as a bug, not an acceptable recovery path. First lock down harness isolation and `0 replan` assertions so failures are explicit. Then remove per-move-family completion/failure gaps in `WalkTemplate`, `AscendTemplate`, and `DescendTemplate`, and finally add longer multi-segment live routes that combine operations without planner partials or executor retries. + +**Tech Stack:** C# 14 / .NET 10, xUnit, MCC local `1.21.11-Vanilla` harness via `tools/mcc-env.sh`, tmux/RCON-driven live tests, MCC pathing core and execution templates. + +--- + +## Current Facts + +Latest live evidence from `/tmp/mcc-debug/*/mcc-debug.log`: + +- `Flat final stop`: `2` replans + - `Traverse` failed at `(101.56, 80.00, 100.74)` + - `Traverse/FinalStop` failed at `(103.36, 80.00, 100.50)` +- `Parkour into turn`: `1` replan + - final `Traverse/FinalStop` failed around `(122.50, 80.00, 111.36)` +- `Corner ascend around wall`: `1` replan + - single `Ascend/FinalStop` failed around `(191.38, 81.00, 171.38)` +- `Wall-adjacent descend`: `1` replan + - single `Descend/FinalStop` failed at `(201.50, 80.00, 200.50)` +- `Ascend chain smoke`: `3` replans + - all failures occur on chained `Ascend` segments before the partial fallback stops at `(177.46, 83.00, 162.50)` +- `Rejected 3x1 no-run-up gap`: currently not a clean reject + - planner returns a `Partial` path, then execution replans before failing +- `Rejected 2x1 side-wall jump`: already correct, `0 replan`, direct `A* Failed` + +## User Constraints + +These constraints override earlier assumptions in this plan: + +- MCC currently has no entity collision implementation that would make other players or mobs perturb these tests. +- Other players being online is not itself a movement interference source for this work. +- Residual yaw/pitch between independent scenarios is not a fix target for this plan; templates already steer every tick. +- Residual speed should be recorded for diagnosis, but not “normalized away” inside a route. +- For long accepted routes, residual speed between internal actions is expected and must not be treated as test interference. The route should still finish with `0 replan`. + +## Interference Inventory + +The harness already disables or controls the environmental variables that matter for this work: + +- `difficulty peaceful` +- `gamerule doMobSpawning false` +- fixed test geometry with `fill`/`setblock` +- explicit `tp` before each scenario + +Remaining sources of ambiguity that still matter before claiming `0 replan`: + +1. Shared live server state + - The workflow uses shared `mc-*` tmux sessions. + - This matters for repeatability and logging, but not because of entity collision. + +2. Reused MCC session state across scenarios + - The harness runs multiple scenarios in the same MCC session. + - Residual speed can carry across scenario boundaries if the next scenario starts too early. + - Yaw/pitch carryover is not considered a blocker for this plan. + +3. Planner timeout / partial-path behavior + - Some “reject” or long-route scenarios currently hit `A* result: Partial`. + - Those cases cannot be used as `0 replan` executor proofs until the route size is kept below timeout, or the test is explicitly categorized as a planner-partial case. + +4. Current live harness acceptance is weaker than target behavior + - The harness was updated to accept “already in goal block” completion. + - That is useful for keeping live validation running, but it currently masks the stronger requirement that accepted deterministic routes should need `0 replan`. + +5. Residual-speed observability is currently weak + - The harness captures final location, but it does not systematically record pre-route speed and per-route terminal speed. + - We should measure speed, not try to zero it between internal actions. + +## Zero-Replan Contract + +For this plan, an accepted live pathing scenario passes only if all of the following are true: + +- `A* result: Success` +- no `A* result: Partial` +- no `Replan #` +- no `Segment .* FAILED` +- no `Replan failed -- no path found` +- final MCC location is inside the intended goal support block +- `PathMgr` reaches `Navigation complete!` + +For rejection scenarios, pass only if: + +- `A* result: Failed` or `No path found` +- no `Navigation started` +- no `Replan #` + +## File Structure + +### Production code + +- `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` + - Traverse and diagonal segment runtime; primary target for straight-line and turn-entry `0 replan`. +- `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` + - Single-step ascend execution; primary target for ascend landing and chained ascend stability. +- `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` + - Landing/final-stop descend execution. +- `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` + - Shared grounded completion and braking gate; likely the common source of “already good enough, but segment still fails one tick later”. +- `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + - Shared geometry and settle helpers. +- `MinecraftClient/Pathing/Execution/PathSegmentManager.cs` + - Replan handling and live navigation orchestration. +- `MinecraftClient/Pathing/Core/AStarPathFinder.cs` + - Planner timeout and partial-path behavior. +- `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` + - Run-up / jump-feasibility checks for the `3x1` rejection case. +- `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` + - Supporting parkour admissibility logic. +- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/path/PathExecutor.java` + - Reference executor behavior for timeout, splice/repath, and movement handoff semantics. +- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java` + - Reference traverse completion semantics. +- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementAscend.java` + - Reference ascend landing and post-jump settle logic. +- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementDescend.java` + - Reference descend safe-mode and landing behavior. +- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementParkour.java` + - Reference parkour admissibility and handoff behavior. + +### Tests and harness + +- `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` + - Deterministic convergence tests for walk, descend, and future ascend/diagonal cases. +- `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + - Manager-level replan and already-in-goal behavior. +- `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs` + - Multi-segment executor behavior. +- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + - Parkour and landing handoff cases. +- `tools/test-transition-braking.sh` + - Short accepted-route live harness. +- `tools/test-pathing-template-regressions.sh` + - Current mixed live regression suite. +- `tools/test-pathing-long-routes.sh` + - New long-route `0 replan` live suite. +- `docs/guide/pathfinding-research.md` + - Pathing behavior contract and live test expectations. + +--- + +### Task 1: Freeze the Zero-Replan Harness Contract + +**Files:** +- Modify: `tools/test-transition-braking.sh` +- Modify: `tools/test-pathing-template-regressions.sh` +- Create: `tools/test-pathing-long-routes.sh` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Add live log helpers that fail on any accepted-route replan** + +Add shell helpers to both existing harness scripts: + +```bash +count_replans_since() { + local from_line="$1" + log_since "$from_line" | grep -Ec "\\[PathMgr\\] Replan #|\\[PathExec\\] Segment .* FAILED" || true +} + +assert_no_replans_since() { + local from_line="$1" + local count + count="$(count_replans_since "$from_line")" + if [[ "$count" != "0" ]]; then + echo "Expected 0 replans, saw $count" >&2 + log_since "$from_line" >&2 + return 1 + fi +} + +assert_no_partial_since() { + local from_line="$1" + if log_since "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then + echo "Expected full success path, saw partial path" >&2 + log_since "$from_line" >&2 + return 1 + fi +} +``` + +- [ ] **Step 2: Normalize only the state that should differ between independent scenarios** + +For accepted live scenarios, add: + +```bash +mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true +mc-rcon "tp $USERNAME 100.5 80 100.5" >/dev/null +sleep 2 +send_mcc "debug state" +``` + +Do not add special yaw/pitch normalization. Record the pre-route state instead. +Do reset position and allow enough time that residual speed from the previous independent scenario is observable in logs. + +- [ ] **Step 3: Separate accepted-route assertions from rejection-route assertions** + +Update accepted-route scenarios to require: + +```bash +wait_for_navigation "$start_line" 30 +assert_no_partial_since "$start_line" +assert_no_replans_since "$start_line" +``` + +Update rejection scenarios to require: + +```bash +wait_for_failure_signal "$start_line" 20 +if log_since "$start_line" | grep -Eq "\\[PathMgr\\] Replan #|\\[PathExec\\] Segment .* FAILED"; then + echo "Expected direct rejection, saw execution replan" >&2 + return 1 +fi +``` + +- [ ] **Step 4: Create a new long-route live harness** + +Create `tools/test-pathing-long-routes.sh` with three accepted-route buckets: + +```bash +run_same_move_routes +run_mixed_move_routes +run_turn_density_routes +``` + +Each accepted route must assert: + +```bash +wait_for_navigation "$start_line" 45 +assert_no_partial_since "$start_line" +assert_no_replans_since "$start_line" +read -r x y z <<< "$(capture_debug_location)" +assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z" +``` + +Each accepted route must also log: + +```bash +capture_debug_state_before_route +capture_debug_state_after_route +``` + +to record start/end speed and location for diagnosis. Do not reset any state between internal actions of a single route. + +- [ ] **Step 5: Document the zero-replan live contract** + +Add a short section to `docs/guide/pathfinding-research.md`: + +```md +### Deterministic live route contract + +For the short-route and long-route 1.21.11 live harnesses, accepted routes must complete with: + +- `A* result: Success` +- `0 replan` +- `0` template segment failures +- final position inside the goal support block + +Rejection scenarios must fail before execution starts. +``` + +- [ ] **Step 6: Run harness baselines and record current failures** + +Run: + +```bash +source tools/mcc-env.sh +bash tools/test-transition-braking.sh 1.21.11-Vanilla +bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected right now: FAIL because the accepted scenarios still produce replans. + +--- + +### Task 2: Baritone Reference Pass Before Code Changes + +**Files:** +- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/path/PathExecutor.java` +- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java` +- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementAscend.java` +- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementDescend.java` +- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementParkour.java` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Extract only the behavior that applies to MCC’s zero-replan goal** + +Summarize, in MCC terms: + +- when Baritone considers a movement complete +- when it keeps controlling after landing instead of immediately failing +- how it handles path executor timeout and repath +- which parkour and descend transitions depend on next-movement awareness + +- [ ] **Step 2: Write down the allowed and disallowed Baritone borrow list** + +Add to `docs/guide/pathfinding-research.md`: + +```md +### Baritone reference notes for zero-replan work + +Borrow: +- landing-aware completion +- next-movement-aware descend/ascend handoff +- conservative parkour admissibility + +Do not borrow: +- GoalBlock occupancy semantics that allow success with sloppy live execution +- executor repath tolerance as a substitute for deterministic harness stability +``` + +- [ ] **Step 3: Do not change MCC code in this task** + +This task is design grounding only. The output is a short written comparison that later tasks can cite. + +--- + +### Task 3: Reproduce Each Replan Family With Deterministic Tests + +**Files:** +- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + +- [ ] **Step 1: Add a walk final-stop red test for the live `(103.36, 80.00, 100.50)` case** + +Add a test that seeds physics at the live near-goal position and expects completion without failure: + +```csharp +[Fact] +public void WalkTemplate_FinalStop_Completes_FromLiveNearGoalState_WithoutFailure() +{ + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115); + var segment = new PathSegment + { + Start = new Location(102.5, 80, 100.5), + End = new Location(103.5, 80, 100.5), + MoveType = MoveType.Traverse, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new WalkTemplate(segment, null); + var physics = new PlayerPhysics + { + Position = new Vec3d(103.36, 80.0, 100.50), + DeltaMovement = new Vec3d(0.0346, 0.0, 0.0), + OnGround = true, + MovementSpeed = 0.1f, + Yaw = 270f + }; + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, 40, out _); + Assert.Equal(TemplateState.Complete, state); +} +``` + +- [ ] **Step 2: Add an ascend final-stop red test for the live `(191.38, 81.00, 171.38)` case** + +```csharp +[Fact] +public void AscendTemplate_FinalStop_Completes_FromLiveLandingState_WithoutFailure() +{ + // build the same corner-ascend world as the live harness + // seed physics at the live failure position + // expect TemplateState.Complete within a short horizon +} +``` + +- [ ] **Step 3: Add a descend final-stop red test for the live `(201.50, 80.00, 200.50)` case** + +```csharp +[Fact] +public void DescendTemplate_FinalStop_Completes_FromLiveLandingState_WithoutFailure() +{ + // build the wall-adjacent descend world + // seed physics at the live failure position + // expect TemplateState.Complete +} +``` + +- [ ] **Step 4: Add a manager-level red test that accepted routes finish with zero replans** + +Extend `PathSegmentManagerTests.cs` with a short accepted path: + +```csharp +[Fact] +public void Tick_ShortAcceptedPath_CompletesWithoutIncrementingReplanCount() +{ + // start manager with a 3-node flat path + // run ticks until completion + // assert manager.ReplanCount == 0 +} +``` + +- [ ] **Step 5: Run the focused red test set** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~GroundedTemplateConvergenceTests|FullyQualifiedName~PathExecutorCompletionTests|FullyQualifiedName~PathSegmentManagerTests|FullyQualifiedName~SprintJumpTemplateScenarioTests" -v minimal +``` + +Expected initially: FAIL on the new live-parity cases. + +--- + +### Task 4: Remove Zero-Replan Gaps In Walk And FinalStop Execution + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + +- [ ] **Step 1: Make `WalkTemplate` prefer completion over fail once goal support is already valid** + +Before the template returns `TemplateState.Failed`, ensure it checks an explicit “good enough for this segment” predicate first: + +```csharp +if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; + +if (_stuckTicks > 40 || _tickCount > maxTicks) + return TemplateState.Failed; +``` + +The final implementation must preserve that order even when the bot is slightly off center but already inside valid goal support. + +- [ ] **Step 2: Tighten final-stop control so the last traverse segment stops without lateral drift** + +Update planner/controller logic for `PathTransitionType.FinalStop` to penalize cross-axis drift near the end plane: + +```csharp +double lateralError = TemplateHelper.CrossTrackDistance(pos, segment); +if (segment.ExitTransition == PathTransitionType.FinalStop && lateralError > 0.10) +{ + // reduce forward carry and bias facing back to segment heading +} +``` + +- [ ] **Step 3: Ensure `ContinueStraight -> FinalStop` handoff drops sprint early enough** + +Use the final live traces as the acceptance reference: + +- flat final stop must not replan at segment `0` +- final stop segment must complete before the `(103.36, 80.00, 100.50)` failure state +- do not rely on yaw/pitch reset to make this pass +- residual speed within the route is allowed as long as the route still completes with `0 replan` + +- [ ] **Step 4: Run focused deterministic tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~WalkTemplate_FinalStop|FullyQualifiedName~PathExecutorCompletionTests|FullyQualifiedName~PathSegmentManagerTests" -v minimal +``` + +Expected: PASS with the new walk/final-stop tests green. + +--- + +### Task 5: Remove Zero-Replan Gaps In Ascend Execution + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` + +- [ ] **Step 1: Add a dedicated post-landing settle phase in `AscendTemplate`** + +Current code always sets: + +```csharp +input.Forward = true; +input.Sprint = true; +``` + +Replace that with a landing-aware phase: + +```csharp +bool landedOnTargetLevel = physics.OnGround && Math.Abs(dy) < 0.2; +if (landedOnTargetLevel) +{ + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; +} +else +{ + input.Forward = true; + input.Sprint = true; + if (physics.OnGround && dy > 0.1) + input.Jump = true; +} +``` + +The acceptance bar is not “zero residual speed after each ascend”. The acceptance bar is “the accepted route continues without a replan”. + +- [ ] **Step 2: Add live-parity ascend tests** + +Add deterministic tests for: + +- corner ascend final stop +- chained ascend middle segment +- chained ascend final segment + +- [ ] **Step 3: Run the focused ascend suite** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Ascend|FullyQualifiedName~Corner ascend|FullyQualifiedName~PathSegmentManagerTests" -v minimal +``` + +Expected: PASS with no new regressions in existing ascend tests. + +--- + +### Task 6: Remove Zero-Replan Gaps In Descend Execution + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs` + +- [ ] **Step 1: Make landing-state final stop complete immediately when support is already valid** + +Preserve the landing-phase completion ordering: + +```csharp +if (physics.OnGround && Math.Abs(dy) < 1.0) +{ + GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world); + if (GroundedSegmentController.ShouldComplete(_segment, pos, physics)) + return TemplateState.Complete; +} +``` + +The acceptance case is the exact live failure state at `(201.50, 80.00, 200.50)`. + +- [ ] **Step 2: Add descend live-parity tests** + +Add tests that seed the exact live landing state and assert `TemplateState.Complete` rather than `Failed`. + +- [ ] **Step 3: Run the focused descend suite** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~DescendTemplate|FullyQualifiedName~GroundedTemplateConvergenceTests" -v minimal +``` + +Expected: PASS with the new descend test green. + +--- + +### Task 7: Make Rejection Scenarios Reject Before Execution Starts + +**Files:** +- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs` +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` +- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` +- Modify: `tools/test-pathing-template-regressions.sh` + +- [ ] **Step 1: Add a deterministic rejection test for the `3x1 no-run-up` case** + +Create or extend a pathfinder-level test that asserts: + +```csharp +Assert.Equal(PathStatus.Failed, result.Status); +Assert.Empty(PathSegmentBuilder.FromPath(result.Path)); +``` + +for the exact live `141.5 -> 144.5/81/138.5` layout. + +- [ ] **Step 2: Tighten parkour admissibility before planner partial fallback is considered acceptable** + +Review the current path: + +- `Traverse -> Ascend` to a partial fallback at `(143,81,138)` + +The fix should make this route non-admissible in the first place if the intended gap cannot be completed with valid run-up. +- Use `ThirdpartyReference/baritone/.../MovementParkour.java` and the prior admissibility spec as references, but keep MCC’s execution contract stricter: direct reject in this harness. + +- [ ] **Step 3: Re-run rejection-only live validation** + +Run: + +```bash +source tools/mcc-env.sh +bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected for rejection scenarios: + +- `2x1 side-wall jump`: direct reject, `0 replan` +- `3x1 no-run-up gap`: direct reject, `0 replan` + +--- + +### Task 8: Add Long-Route Zero-Replan Stability Coverage + +**Files:** +- Create: `tools/test-pathing-long-routes.sh` +- Modify: `docs/guide/pathfinding-research.md` + +- [ ] **Step 1: Add same-operation long routes** + +Use route lengths that remain comfortably below planner timeout: + +- straight traverse chain: `8-12` blocks +- diagonal zig-zag chain: `6-8` segments +- ascend staircase: `4-6` ascends +- descend staircase: `4-6` descends +- aligned parkour chain: `3-4` jumps + +Each must require: + +- `A* result: Success` +- `0 replan` +- final location inside goal block + +- [ ] **Step 2: Add mixed-operation long routes** + +Recommended mixed routes: + +- `Traverse -> Turn -> Parkour -> Turn -> Traverse -> FinalStop` +- `Diagonal -> Ascend -> Traverse -> Descend -> FinalStop` +- `Traverse -> Ascend -> Traverse -> Parkour -> Descend -> FinalStop` + +Keep all mixed routes inside a small pre-cleared test region so chunk loading is not the variable under test. +Do not reset speed or orientation between internal actions of a route. A route only passes if the naturally carried speed across those actions still yields `0 replan`. + +- [ ] **Step 3: Add turn-density routes** + +Add one route with frequent heading changes and no jumps: + +- `8-10` short traverse/diagonal segments +- every segment should change heading +- must still finish with `0 replan` + +- [ ] **Step 4: Add speed-carry long routes** + +Add one route each for: + +- repeated `Traverse -> Ascend` +- repeated `Traverse -> Descend` +- repeated `Traverse -> Parkour` + +These routes exist specifically to prove that residual speed between actions does not force replans in deterministic conditions. + +- [ ] **Step 5: Run the long-route harness** + +Run: + +```bash +source tools/mcc-env.sh +bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla +``` + +Expected: PASS with every accepted route showing `0 replan`. + +--- + +### Task 9: Full Verification + +**Files:** +- No new files + +- [ ] **Step 1: Run deterministic test coverage** + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal +``` + +Expected: PASS, `0 failed`. + +- [ ] **Step 2: Run release build** + +```bash +source tools/mcc-env.sh +mcc-build +``` + +Expected: `Build succeeded. 0 Warning(s), 0 Error(s)`. + +- [ ] **Step 3: Run short live suites** + +```bash +source tools/mcc-env.sh +bash tools/test-transition-braking.sh 1.21.11-Vanilla +bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla +``` + +Expected: + +- accepted scenarios: `0 replan` +- rejection scenarios: direct reject, `0 replan` + +- [ ] **Step 4: Run long-route live suite** + +```bash +source tools/mcc-env.sh +bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla +``` + +Expected: PASS, no accepted route triggers `replan`. + +## Self-Review + +Spec coverage: + +- `0 replan` short deterministic live scenarios: covered by Tasks 1-6 +- interference inventory first: covered near top of this plan, revised to remove non-applicable entity and yaw/pitch concerns +- Baritone comparison before code changes: covered by Task 2 +- rejection cleanup: covered by Task 7 +- long-path stability coverage, including speed-carry routes: covered by Task 8 + +Placeholder scan: + +- No `TODO` or `TBD` placeholders remain +- Concrete files, scenarios, and commands are included + +Type consistency: + +- File paths and move/template names match current codebase names: + - `WalkTemplate` + - `AscendTemplate` + - `DescendTemplate` + - `GroundedSegmentController` + - `AStarPathFinder` diff --git a/docs/superpowers/plans/2026-04-16-linear-parallel-zero-replan.md b/docs/superpowers/plans/2026-04-16-linear-parallel-zero-replan.md new file mode 100644 index 0000000000..d261a8bfe9 --- /dev/null +++ b/docs/superpowers/plans/2026-04-16-linear-parallel-zero-replan.md @@ -0,0 +1,618 @@ +# Linear Parallel Zero-Replan 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:** Make the `tools/test-parkour.py --filter linear --parallel 6` run on `1.21.11-Vanilla` execute all theory-allowed `linear` cases as `pass`, all theory-forbidden cases as `reject`, and detect any `replan` or turn-in-place stall as a test failure. + +**Architecture:** Treat the current problem as two coupled systems. First, harden the live harness so parallel runs are trustworthy: worker startup must be deterministic, outcome classification must parse `[PathMetric]` telemetry, and the runner must detect turn-in-place stalls instead of inferring success from `Navigation complete!` plus a loose proximity check. Second, add focused C# regressions for the four currently exposed linear failures, then tune the parkour execution templates and grounded completion thresholds until those scenarios complete without replans, overshoot, or stall behavior. + +**Tech Stack:** Python 3, pytest/unittest, C# 14 /.NET 10, MCC pathing runtime, local `1.21.11-Vanilla` server harness, RCON, tmux-backed `mcc-debug`. + +--- + +## Current Facts + +- Latest valid executed parallel live run used: + - `python3 tools/test-parkour.py --filter linear --parallel 6 --version 1.21.11-Vanilla --results /tmp/linear-live-valid-20260416.jsonl` +- That run built all 25 courses, launched 6 workers, and executed 13 cases before group-level stop-at-first-failure logic skipped the rest of each failing group. +- Current observed live mismatches are: + - `linear-flat-gap1`: expected `pass`, got `fail` + - `linear-ascend-gap2-dy+1`: expected `pass`, got `fail` + - `linear-descend-gap2-dy-2`: expected `pass`, got `fail` + - `linear-descend-gap4-dy-1`: expected `pass`, got `fail` +- Current harness behavior is now trustworthy for this task: + - parses `[PathMetric]` telemetry + - fails any pass-case with `replan_count > 0` + - fails any pass-case with turn-stall detection + - persists `replan_count`, `turn_stall_count`, `near_goal`, `total_ticks`, and `final_position` to JSONL +- Current C# regression status is narrower than live status: + - targeted `SprintJumpTemplate` regressions were added and used to drive several execution fixes + - local C# red lights no longer fully predict the live failure surface + - the next TDD cycle must add regression coverage for `linear-flat-gap1`, `linear-ascend-gap2-dy+1`, and `linear-descend-gap4-dy-1`, not just the original four +- The parallel worker startup bug in `.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh` remains fixed and covered by tests. +- Local references are available if needed: + - Decompiled server source under `$MCC_REPO/MinecraftOfficial/-decompiled/` + - `ThirdpartyReference/baritone` + +## File Structure + +### Harness / integration loop + +- Modify: `.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh` + - Fix ambiguous argument parsing when the output ini already exists. +- Modify: `tools/test-parkour.py` + - Add strict live metrics parsing. + - Add turn-in-place stall detection during navigation. + - Persist replan/turn metrics into JSONL. + - Keep parallel worker behavior, but make results trustworthy. +- Modify: `tools/tests/test_pathing_live_scripts.py` + - Keep startup/config regression coverage and align list-case assertions with the current script interface. +- Create: `tools/tests/test_test_parkour_metrics.py` + - Focused tests for log parsing, outcome classification, and turn-in-place detection. + +### Runtime / movement fixes + +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs` + - Shared helper to construct the same linear layouts the live harness uses. +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + - Planner/executor regressions that mirror the current valid live mismatches. +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + - Fine-grained parkour landing / overshoot regressions, including currently live-failing flat/ascend/descend chains. +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + - No-replan regressions for accepted linear chains and any newly observed live failures. +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + - Air-brake and landing-completion behavior for long flat / falling jumps. +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` + - Final-stop and prepare-jump completion thresholds. +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` + - Lookahead braking when the next expected state is a true stop. +- Modify: `MinecraftClient/Pathing/Execution/TemplateHelper.cs` + - Extract any shared “still moving too fast to count as settled” helpers used by the above. + +## Task 1: Lock Down The Parallel Harness Contract + +**Files:** +- Modify: `.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh` +- Modify: `tools/tests/test_pathing_live_scripts.py` + +- [ ] **Step 1: Write the failing config regression test** + +Add this test to `tools/tests/test_pathing_live_scripts.py`: + +```python +def test_prepare_offline_config_treats_existing_output_ini_as_output_not_template(self) -> None: + with tempfile.TemporaryDirectory() as tempdir: + temp_path = Path(tempdir) + output_ini = temp_path / "MinecraftClient.debug.ini" + output_ini.write_text( + "\n".join( + [ + "[Main.General]", + 'Account = { Login = "OldBot", Password = "" }', + 'AccountType = "microsoft"', + "", + "[Main.Advanced]", + 'MinecraftVersion = "auto"', + "TerrainAndMovements = false", + "InventoryHandling = false", + "EntityHandling = false", + "AutoRespawn = false", + "", + ] + ), + encoding="utf-8", + ) + + result = subprocess.run( + [ + "bash", + str(REPO_ROOT / ".skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh"), + str(output_ini), + "1.21.11", + "MCCBot1", + ], + check=False, + capture_output=True, + text=True, + cwd=temp_path, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse((temp_path / "1.21.11").exists()) + content = output_ini.read_text(encoding="utf-8") + self.assertIn('AccountType = "mojang"', content) +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_pathing_live_scripts.py -k existing_output_ini_as_output_not_template +``` + +Expected: + +```text +FAILED ... AssertionError: True is not false +``` + +- [ ] **Step 3: Implement the minimal parser fix** + +Update `.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh`: + +```bash +if [[ $# -ge 3 && "$2" == *.ini ]]; then + TEMPLATE_INI="$1" + OUTPUT_INI="$2" + MC_VERSION="$3" + LOGIN_NAME="${4:-MCCBot}" +else + OUTPUT_INI="$1" + MC_VERSION="$2" + LOGIN_NAME="${3:-MCCBot}" +fi +``` + +- [ ] **Step 4: Run the full live-script test file** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_pathing_live_scripts.py +``` + +Expected: + +```text +5 passed +``` + +- [ ] **Step 5: Commit** + +```bash +git add .skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh \ + tools/tests/test_pathing_live_scripts.py +git commit -m "test-harness: fix parallel worker config bootstrap" +``` + +## Task 2: Make The Harness Enforce Zero Replan And Turn-Stall Failures + +**Files:** +- Create: `tools/tests/test_test_parkour_metrics.py` +- Modify: `tools/test-parkour.py` + +- [ ] **Step 1: Write parser/classification tests first** + +Create `tools/tests/test_test_parkour_metrics.py` with focused tests like: + +```python +def test_classify_outcome_pass_requires_route_complete_and_zero_replans(): + metrics = LiveMetrics( + planner_status="Success", + route_complete_count=1, + navigation_complete_count=1, + replan_count=0, + turn_stall_count=0, + ) + assert classify_outcome(metrics) == "pass" + + +def test_classify_outcome_replan_is_fail(): + metrics = LiveMetrics( + planner_status="Success", + route_complete_count=1, + navigation_complete_count=1, + replan_count=1, + turn_stall_count=0, + ) + assert classify_outcome(metrics) == "fail" + + +def test_classify_outcome_turn_stall_is_fail(): + metrics = LiveMetrics( + planner_status="Success", + route_complete_count=1, + navigation_complete_count=1, + replan_count=0, + turn_stall_count=1, + ) + assert classify_outcome(metrics) == "fail" + + +def test_detect_turn_stall_requires_low_motion_and_large_yaw_change(): + samples = [ + NavigationSample(x=100.5, y=80.0, z=100.5, yaw=0.0), + NavigationSample(x=100.6, y=80.0, z=100.5, yaw=95.0), + NavigationSample(x=100.6, y=80.0, z=100.5, yaw=185.0), + ] + assert detect_turn_stall(samples) is True +``` + +- [ ] **Step 2: Run the new tests to verify they fail** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +``` + +Expected: + +```text +FAILED ... NameError / AttributeError for LiveMetrics, classify_outcome, NavigationSample, detect_turn_stall +``` + +- [ ] **Step 3: Add strict live metrics and turn-stall sampling** + +In `tools/test-parkour.py`, add the minimal structures and parsing helpers: + +```python +@dataclass +class NavigationSample: + x: float + y: float + z: float + yaw: float + + +@dataclass +class LiveMetrics: + planner_status: str | None = None + route_complete_count: int = 0 + navigation_complete_count: int = 0 + replan_count: int = 0 + replan_failed_count: int = 0 + segment_failed_count: int = 0 + turn_stall_count: int = 0 + + +def classify_outcome(metrics: LiveMetrics) -> str: + if metrics.replan_count or metrics.replan_failed_count or metrics.segment_failed_count or metrics.turn_stall_count: + return "fail" + if metrics.planner_status in {"Partial", "Failed"}: + return "reject" + if metrics.route_complete_count or metrics.navigation_complete_count: + return "pass" + return "invalid_live_case" +``` + +Also add navigation polling that samples both position and rotation during `wait_seconds`: + +```python +def get_player_pose(rcon: RconClient, username: str) -> NavigationSample | None: + pos = rcon.command(f"data get entity {username} Pos") + rot = rcon.command(f"data get entity {username} Rotation") + ... + return NavigationSample(x, y, z, yaw) +``` + +```python +def detect_turn_stall(samples: list[NavigationSample]) -> bool: + if len(samples) < 3: + return False + total_motion = sum(math.dist((a.x, a.z), (b.x, b.z)) for a, b in zip(samples, samples[1:])) + total_yaw = sum(abs(normalize_yaw_delta(b.yaw - a.yaw)) for a, b in zip(samples, samples[1:])) + return total_motion < 1.0 and total_yaw >= 180.0 +``` + +- [ ] **Step 4: Persist the new metrics into JSONL** + +Extend the JSONL write in `tools/test-parkour.py`: + +```python +f.write(json.dumps({ + "case_id": case.case_id, + "expected": case.expected, + "outcome": result.outcome, + "matched": result.matched_expected, + "worker": worker_id, + "planner_status": result.metrics.planner_status, + "replan_count": result.metrics.replan_count, + "replan_failed_count": result.metrics.replan_failed_count, + "segment_failed_count": result.metrics.segment_failed_count, + "turn_stall_count": result.metrics.turn_stall_count, +}) + "\n") +``` + +- [ ] **Step 5: Verify the harness tests pass** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +python3 -m pytest -q tools/tests/test_pathing_live_scripts.py +``` + +Expected: + +```text +all green +``` + +- [ ] **Step 6: Commit** + +```bash +git add tools/test-parkour.py \ + tools/tests/test_test_parkour_metrics.py \ + tools/tests/test_pathing_live_scripts.py +git commit -m "test-harness: enforce zero-replan and turn-stall failures" +``` + +## Task 3: Reproduce The Four Live Linear Failures In C# Tests + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + +- [ ] **Step 1: Add a shared linear world builder helper** + +Create `MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs`: + +```csharp +using MinecraftClient.Mapping; + +namespace MinecraftClient.Tests.Pathing.Execution.Scenarios; + +internal static class LinearParkourScenarioBuilder +{ + internal static World Build(int gap, int deltaY, out Location start, out Location end) + { + World world = FlatWorldTestBuilder.CreateStoneFloor(min: 90, max: 140); + FlatWorldTestBuilder.ClearBox(world, 96, 70, 96, 132, 90, 104); + + start = new Location(100.5, 80, 100.5); + int floorY = 79; + FlatWorldTestBuilder.FillSolid(world, 100, floorY, 100, 103, floorY, 100); + + int lastX = 103; + int lastY = floorY; + for (int i = 0; i < 3; i++) + { + int platX = lastX + gap + 1; + int platY = lastY + deltaY; + FlatWorldTestBuilder.SetSolid(world, platX, platY, 100); + lastX = platX; + lastY = platY; + } + + end = new Location(lastX + 0.5, lastY + 1, 100.5); + return world; + } +} +``` + +- [ ] **Step 2: Write the failing manager-level regressions** + +Extend `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs`: + +```csharp +[Fact] +public void Tick_LinearAscendGap1DyPlus1_CompletesWithoutReplan() +{ + World world = LinearParkourScenarioBuilder.Build(gap: 1, deltaY: 1, out Location start, out Location end); + var result = BuildLinearPathResult(start, end, MoveType.Parkour); + var manager = new PathSegmentManager(debugLog: _ => { }, infoLog: _ => { }); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(start, yaw: 270f); + var input = new MovementInput(); + + manager.StartNavigation(new GoalBlock((int)Math.Floor(end.X), (int)end.Y, (int)Math.Floor(end.Z)), result); + RunManager(manager, physics, input, world, maxTicks: 420); + + Assert.False(manager.IsNavigating); + Assert.Equal(0, manager.ReplanCount); + Assert.True(Math.Abs(physics.Position.X - end.X) < 1.0); +} +``` + +Mirror the same structure for: +- `gap: 2, deltaY: -2` +- `gap: 3, deltaY: -1` +- `gap: 4, deltaY: 0` + +- [ ] **Step 3: Run those tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathSegmentManagerTests|FullyQualifiedName~LivePathingRegressionTests|FullyQualifiedName~SprintJumpTemplateScenarioTests" -v minimal +``` + +Expected: + +```text +FAIL with at least the four new linear regressions +``` + +- [ ] **Step 4: Add lower-level template regressions for overshoot / fall** + +Extend `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` with: + +```csharp +[Fact] +public void SprintJumpTemplate_LinearFlatGap4_FinalStop_StopsInsideTargetBlock() { ... } + +[Fact] +public void SprintJumpTemplate_LinearDescendGap3DyMinus1_FinalStop_DoesNotOvershoot() { ... } + +[Fact] +public void SprintJumpTemplate_LinearAscendGap1DyPlus1_FinalStop_DoesNotFallAfterLanding() { ... } +``` + +These tests should assert both: + +```csharp +Assert.Equal(TemplateState.Complete, state); +Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); +``` + +- [ ] **Step 5: Commit the failing tests only after they are green later** + +No commit here yet. Keep them staged with the implementation in Task 4. + +## Task 4: Fix Parkour Execution For The Four Exposed Linear Failures + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs` +- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + +- [ ] **Step 1: Fix long-jump overshoot before touching completion gates** + +In `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs`, prefer early release for true final stops when the predicted holding path overshoots the landing block: + +```csharp +if (_segment.ExitTransition == PathTransitionType.FinalStop && !physics.OnGround) +{ + Location? landingIfHolding = PredictLandingPosition(physics, world, holdForward: true, holdSprint: true); + Location? landingIfReleased = PredictLandingPosition(physics, world, holdForward: false, holdSprint: false); + + if (landingIfHolding is not null + && landingIfReleased is not null + && !TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfHolding.Value, ExpectedEnd) + && TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfReleased.Value, ExpectedEnd)) + { + input.Forward = false; + input.Sprint = false; + } +} +``` + +- [ ] **Step 2: Tighten final-stop completion so “complete” cannot happen short of the block** + +In `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`: + +```csharp +if (segment.ExitTransition == PathTransitionType.FinalStop + && physics.OnGround + && TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, segment.End) + && !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, segment.End)) +{ + return TemplateHelper.GetHorizontalSpeed(physics) <= 0.05; +} +``` + +Do not allow `FinalStop` completion through looser `IsNear(...)` checks. + +- [ ] **Step 3: Give landing-recovery / prepare-jump transitions a stricter settle plane** + +In `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`, keep `PrepareJump` from completing while the player is still materially before the end plane: + +```csharp +if (segment.MoveType == MoveType.Parkour + && segment.ExitTransition == PathTransitionType.PrepareJump) +{ + return physics.OnGround + && TemplateHelper.RemainingDistanceAlongSegment(pos, segment) <= 0.20 + && exitSpeed >= segment.ExitHints.MinExitSpeed; +} +``` + +- [ ] **Step 4: Re-run the focused C# regressions until green** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplateScenarioTests|FullyQualifiedName~PathSegmentManagerTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: + +```text +PASS for the four new linear regressions and no collateral failures in the touched suites +``` + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs \ + MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs \ + MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient.Tests/Pathing/Execution/Scenarios/LinearParkourScenarioBuilder.cs \ + MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs \ + MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs +git commit -m "pathing: stabilize linear parkour landings" +``` + +## Task 5: Prove The Full Parallel Linear Matrix On 1.21.11 + +**Files:** +- Modify: `tools/test-parkour.py` only if the previous tasks revealed missing diagnostics +- Verify: `/tmp/main-linear-after-update-parallel-fixed.jsonl` replacement run + +- [ ] **Step 1: Run the strict parallel linear sweep** + +Run: + +```bash +export MCC_SERVERS=/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/MinecraftOfficial/downloads +python3 tools/test-parkour.py --filter linear --parallel 6 --results /tmp/linear-parallel-zero-replan-final.jsonl +``` + +Expected: + +```text +25/25 matched expectations +``` + +- [ ] **Step 2: Machine-check the JSONL** + +Run: + +```bash +python3 - <<'PY' +import json +from pathlib import Path +rows = [json.loads(line) for line in Path('/tmp/linear-parallel-zero-replan-final.jsonl').read_text().splitlines() if line.strip()] +print('rows', len(rows)) +print('mismatches', sum(not r['matched'] for r in rows)) +print('pass_replan_nonzero', sum(r['outcome'] == 'pass' and r.get('replan_count', 0) != 0 for r in rows)) +print('pass_turn_nonzero', sum(r['outcome'] == 'pass' and r.get('turn_stall_count', 0) != 0 for r in rows)) +PY +``` + +Expected: + +```text +rows 25 +mismatches 0 +pass_replan_nonzero 0 +pass_turn_nonzero 0 +``` + +- [ ] **Step 3: Save the observed failing logs if anything remains** + +If the run is not green, copy the live log roots before retrying: + +```bash +cp /tmp/main-linear-after-update-parallel-fixed.jsonl /tmp/linear-parallel-investigation-last.jsonl +cp /tmp/mcc-debug/parkour-*/mcc-debug.log /tmp/ 2>/dev/null || true +``` + +- [ ] **Step 4: Commit the final harness/result adjustments** + +```bash +git add tools/test-parkour.py tools/tests/test_test_parkour_metrics.py +git commit -m "test-harness: verify linear zero-replan parallel sweep" +``` + +## Self-Review + +- Spec coverage: + - Parallel run on `1.21.11`: covered in Task 5. + - Collect test results first: covered in Current Facts and Task 5. + - Complete all theory-allowed `linear` passes: covered in Tasks 3-5. + - Zero replan detection: covered in Task 2 and Task 5 JSONL validation. + - Zero turn-in-place detection: covered in Task 2 via turn-stall sampling and Task 5 JSONL validation. + - Autonomous TDD flow: every production change task begins with failing tests. +- Placeholder scan: + - No `TODO`/`TBD`. + - Every code-change task names exact files and commands. +- Type consistency: + - `LiveMetrics`, `NavigationSample`, `detect_turn_stall`, and `classify_outcome` are named consistently across harness tasks. + - `LinearParkourScenarioBuilder.Build(...)` is reused consistently across C# regression tasks. diff --git a/docs/superpowers/plans/2026-04-18-parkour-worker-pool.md b/docs/superpowers/plans/2026-04-18-parkour-worker-pool.md new file mode 100644 index 0000000000..25ca2935a2 --- /dev/null +++ b/docs/superpowers/plans/2026-04-18-parkour-worker-pool.md @@ -0,0 +1,402 @@ +# Parkour Worker Pool 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:** Refactor `tools/test-parkour.py` into a long-lived worker-pool harness that handles server readiness cleanly and emits inspectable per-run artifacts for unattended parallel live testing. + +**Architecture:** Keep the existing matrix generation and live outcome rules, but replace per-case MCC relaunch with stable worker contexts that are reset between cases. Add a run-artifact layer so each case and worker has traceable logs, and classify harness failures separately from product failures. + +**Tech Stack:** Python 3, unittest/pytest, MCC `mcc-debug`, local `1.21.11-Vanilla` server, RCON, tmux-backed worker sessions. + +--- + +### Task 1: Lock The New Result Schema With Tests + +**Files:** +- Modify: `tools/tests/test_test_parkour_metrics.py` +- Test: `tools/tests/test_test_parkour_metrics.py` + +- [ ] **Step 1: Write failing tests for run artifacts and skip rows** + +Add tests that assert: + +```python +def test_result_to_record_includes_worker_session_and_paths(self) -> None: + case = module.TestCase( + case_id="linear-flat-gap1", + family="linear", + subfamily="flat", + gap_or_wall=1, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + result = module.TestResult( + case=case, + outcome="pass", + matched_expected=True, + replan_count=0, + turn_stall_count=0, + near_goal=True, + total_ticks=42, + final_position=(109.5, 80.0, 200.5), + session="parkour-run-1", + log_path="/tmp/parkour-runs/run-1/workers/1/worker.log", + event_log_path="/tmp/parkour-runs/run-1/events.jsonl", + duration_ms=4200, + error_kind=None, + skip_reason=None, + ) + + record = module.result_to_record(result, worker_id=1) + + self.assertEqual(record["session"], "parkour-run-1") + self.assertEqual(record["worker"], 1) + self.assertEqual(record["duration_ms"], 4200) + self.assertEqual(record["skip_reason"], None) + + +def test_make_skip_result_marks_case_as_skipped(self) -> None: + case = module.TestCase( + case_id="linear-flat-gap4", + family="linear", + subfamily="flat", + gap_or_wall=4, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + + result = module.make_skip_result(case, "group_failed_earlier") + + self.assertEqual(result.outcome, "skipped") + self.assertEqual(result.skip_reason, "group_failed_earlier") + self.assertFalse(result.matched_expected) +``` + +- [ ] **Step 2: Run the focused test file and confirm RED** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +``` + +Expected: failures complaining that `TestResult` lacks the new fields and `make_skip_result` does not exist. + +- [ ] **Step 3: Implement the minimal production fields and helpers** + +Update `tools/test-parkour.py` so `TestResult` contains: + +```python +session: str | None = None +log_path: str | None = None +event_log_path: str | None = None +duration_ms: int | None = None +error_kind: str | None = None +skip_reason: str | None = None +``` + +Add: + +```python +def make_skip_result(case: TestCase, reason: str) -> TestResult: + return TestResult( + case=case, + outcome="skipped", + matched_expected=False, + skip_reason=reason, + error_kind=None, + ) +``` + +- [ ] **Step 4: Re-run the focused tests and confirm GREEN** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +``` + +Expected: all tests in that file pass. + +### Task 2: Add Readiness And Harness Error Coverage + +**Files:** +- Modify: `tools/tests/test_test_parkour_metrics.py` +- Modify: `tools/test-parkour.py` + +- [ ] **Step 1: Write failing tests for readiness and harness classification** + +Add tests like: + +```python +def test_classify_outcome_prefers_harness_error_when_rcon_is_unavailable(self) -> None: + metrics = module.LiveMetrics() + result = module.classify_outcome(metrics, near_goal=None, error_kind="harness_rcon_unavailable") + self.assertEqual(result, "harness_rcon_unavailable") + + +def test_wait_for_rcon_ready_retries_until_command_succeeds(self) -> None: + attempts = {"count": 0} + + class FakeRcon: + def command(self, _cmd: str) -> str: + attempts["count"] += 1 + if attempts["count"] < 3: + raise ConnectionRefusedError("not ready") + return "There are 0 of a max of 20 players online" + + with mock.patch.object(module.time, "sleep", lambda _seconds: None): + self.assertTrue(module.wait_for_rcon_ready(FakeRcon(), timeout_seconds=3.0, poll_interval=0.1)) +``` + +- [ ] **Step 2: Run the focused tests and confirm RED** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py -k "harness_error or rcon_ready" +``` + +Expected: failures because the readiness helper and new classification signature do not exist. + +- [ ] **Step 3: Implement minimal readiness helpers** + +Add to `tools/test-parkour.py`: + +```python +def wait_for_rcon_ready( + rcon: RconClient, + timeout_seconds: float = 20.0, + poll_interval: float = 0.5, +) -> bool: + deadline = time.monotonic() + timeout_seconds + while time.monotonic() < deadline: + try: + rcon.command("list") + return True + except Exception: + time.sleep(poll_interval) + return False +``` + +Update `classify_outcome` to accept `error_kind: str | None = None` and return the harness error directly when present. + +- [ ] **Step 4: Re-run focused tests and confirm GREEN** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py -k "harness_error or rcon_ready" +``` + +Expected: passing tests. + +### Task 3: Convert Per-Case Relaunch Into A Long-Lived Worker Pool + +**Files:** +- Modify: `tools/tests/test_test_parkour_metrics.py` +- Modify: `tools/test-parkour.py` + +- [ ] **Step 1: Write failing tests for worker reuse bookkeeping** + +Add tests asserting that one worker can execute multiple cases without changing session naming: + +```python +def test_build_worker_session_name_is_stable_for_multiple_cases(self) -> None: + self.assertEqual(module.build_worker_session_name("run123", 2), "parkour-run123-2") + + +def test_result_rows_can_share_one_worker_session_across_cases(self) -> None: + case1 = "linear-flat-gap1" + case2 = "linear-flat-gap2" + session = module.build_worker_session_name("run123", 2) + self.assertEqual(session, "parkour-run123-2") + self.assertEqual(session, module.build_worker_session_name("run123", 2)) +``` + +- [ ] **Step 2: Run the focused tests and confirm RED if needed** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py -k "worker_session" +``` + +Expected: either existing coverage passes already or new assertions fail because the worker model still depends on per-case session allocation elsewhere. + +- [ ] **Step 3: Implement long-lived worker contexts** + +In `tools/test-parkour.py`: + +- keep `build_worker_session_name()` as the canonical stable session name +- stop calling `build_case_session_name()` from `worker_loop()` +- launch one `WorkerContext` per thread at worker start +- add `reset_worker_state(ctx, layout)` to reposition and resync between cases +- restart only that worker when reset or health checks fail + +The main shape should become: + +```python +ctx = ensure_worker_context(...) +for case, layout in items: + if case.group_key() in failed_groups: + ... + continue + + ctx = ensure_worker_context(...) + reset = reset_worker_state(ctx, layout) + if not reset.ok: + cleanup_workers([ctx]) + ctx = relaunch_worker_context(...) + reset = reset_worker_state(ctx, layout) +``` + +- [ ] **Step 4: Re-run focused metric tests** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +``` + +Expected: file stays green after the worker-pool refactor. + +### Task 4: Emit Run Directories, Per-Case Records, And Summaries + +**Files:** +- Modify: `tools/tests/test_test_parkour_metrics.py` +- Modify: `tools/test-parkour.py` + +- [ ] **Step 1: Write failing tests for summary aggregation** + +Add tests like: + +```python +def test_summarize_results_groups_by_family_and_outcome(self) -> None: + summary = module.summarize_results( + [ + {"family": "linear", "outcome": "pass", "matched": True}, + {"family": "linear", "outcome": "reject", "matched": True}, + {"family": "neo", "outcome": "reject", "matched": False}, + {"family": "linear", "outcome": "skipped", "matched": False}, + ] + ) + + self.assertEqual(summary["families"]["linear"]["outcomes"]["pass"], 1) + self.assertEqual(summary["families"]["linear"]["outcomes"]["skipped"], 1) + self.assertEqual(summary["families"]["neo"]["mismatches"], 1) +``` + +- [ ] **Step 2: Run the focused tests and confirm RED** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py -k summarize_results +``` + +Expected: missing helper failures. + +- [ ] **Step 3: Implement run-artifact helpers** + +Add helpers in `tools/test-parkour.py`: + +```python +def create_run_dir(base_dir: Path | None = None) -> Path: ... +def write_case_artifact(run_dir: Path, result: TestResult) -> None: ... +def summarize_results(records: list[dict[str, object]]) -> dict[str, object]: ... +def write_summary_files(run_dir: Path, summary: dict[str, object]) -> None: ... +``` + +Update `_run_parallel()` and `_run_serial()` to: + +- create one run directory +- append JSONL rows there even when `--results` is omitted +- persist skip rows +- write `summary.json` and `summary.md` + +- [ ] **Step 4: Re-run targeted tests and then full tools suite** + +Run: + +```bash +python3 -m pytest -q tools/tests/test_test_parkour_metrics.py +python3 -m pytest -q tools/tests +``` + +Expected: full `tools/tests` suite passes. + +### Task 5: Verify The Real Harness Fixes Against 1.21.11 + +**Files:** +- Modify: `tools/test-parkour.py` +- Test: real-server execution only + +- [ ] **Step 1: Run a filtered live smoke on linear with long-lived workers** + +Run: + +```bash +source tools/mcc-env.sh && \ +python3 tools/test-parkour.py \ + --filter linear \ + --parallel 6 \ + --version 1.21.11-Vanilla \ + --results /tmp/parkour-linear-worker-pool.jsonl +``` + +Expected: + +- worker startup lines show six stable worker sessions +- multiple cases reuse the same worker/session ids +- summary artifacts are written under `/tmp/parkour-runs/...` + +- [ ] **Step 2: Inspect the emitted summary and worker logs** + +Run: + +```bash +latest_run=$(ls -td /tmp/parkour-runs/* | head -n 1) +printf '%s\n' "$latest_run" +sed -n '1,220p' "$latest_run/summary.md" +find "$latest_run/workers" -maxdepth 2 -type f | sort +``` + +Expected: + +- `summary.md` exists +- worker logs exist +- skipped cases are represented in the summary + +- [ ] **Step 3: Run one full live matrix to prove unattended output quality** + +Run: + +```bash +source tools/mcc-env.sh && \ +python3 tools/test-parkour.py \ + --parallel 6 \ + --version 1.21.11-Vanilla \ + --results /tmp/parkour-full-worker-pool.jsonl +``` + +Expected: + +- no raw `ConnectionRefusedError` at startup when the server is merely late +- a completed `summary.md` and `summary.json` +- case rows include `worker`, `session`, `log_path`, `duration_ms`, and skip metadata + +- [ ] **Step 4: Commit** + +```bash +git add tools/test-parkour.py \ + tools/tests/test_test_parkour_metrics.py \ + tools/tests/test_pathing_live_scripts.py \ + docs/superpowers/specs/2026-04-18-parkour-worker-pool-design.md \ + docs/superpowers/plans/2026-04-18-parkour-worker-pool.md +git commit -m "test-harness: add long-lived parkour worker pool" +``` diff --git a/docs/superpowers/plans/2026-04-18-sidewall-parkour-zero-replan.md b/docs/superpowers/plans/2026-04-18-sidewall-parkour-zero-replan.md new file mode 100644 index 0000000000..7ff3fec160 --- /dev/null +++ b/docs/superpowers/plans/2026-04-18-sidewall-parkour-zero-replan.md @@ -0,0 +1,1035 @@ +# Sidewall Parkour Zero-Replan 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:** Make all `sidewall` cases in `tools/test-parkour.py --filter sidewall --parallel 6 --version 1.21.11-Vanilla` match theory on `1.21.11-Vanilla`, with every accepted case completing at `replan_count=0` and `turn_stall_count=0`, while preserving the current all-green `linear` matrix. + +**Architecture:** Isolate sidewall behavior instead of loosening the generic linear parkour logic. Add an explicit `ParkourProfile.Sidewall` planner-to-executor profile that the planner can admit using dominant-axis runway rules and the executor can follow using a straight runway approach plus controlled in-air bias toward the landing block. Freeze the exact `tools/test-parkour.py` sidewall geometry in .NET regressions first, then implement planner and executor changes behind those tests, and do not call the work done until `sidewall` is `30/30` and `linear` remains `22/22`. + +**Tech Stack:** C# 14 / .NET 10, xUnit, MCC pathing core/execution, Python 3 live harness `tools/test-parkour.py`, local `1.21.11-Vanilla` server via `tools/mcc-env.sh`. + +--- + +## Scope And Guardrails + +- In scope: `sidewall/flat`, `sidewall/ascend`, and `sidewall/descend` for `wo=0` and `wo=1`, using the exact live geometry from `tools/test-parkour.py`. +- Hard requirement: every accepted sidewall case must finish with `replan_count=0` and `turn_stall_count=0`. +- Hard requirement: `linear` is already fully green in live runs. Do not weaken or rewrite the existing cardinal linear parkour rules just to make sidewall pass. +- Acceptance gate for this plan: targeted green .NET regressions plus `tools/test-parkour.py` sidewall and linear live matrices. Do not use the current full `MinecraftClient.Tests` suite as the gate because the baseline is already `181/198` with 17 unrelated failures. +- Execution note: the fresh baseline evidence came from branch `pathing/jump-entry-direct-yaw` in the main workspace, not an isolated worktree. If the user keeps work in this workspace, do not reset or discard unrelated changes. +- Out of scope for this plan: `neo` and `ceiling` live mismatches. If a helper becomes reusable for those families later, keep it generic, but do not expand verification targets in this plan. + +## File Structure + +- Create: `MinecraftClient/Pathing/Core/ParkourProfile.cs` + Responsibility: explicit planner-to-executor profile for `Default` vs `Sidewall` parkour. +- Modify: `MinecraftClient/Pathing/Core/MoveResult.cs` + Responsibility: carry `ParkourProfile` out of `IMove.Calculate()`. +- Modify: `MinecraftClient/Pathing/Core/PathNode.cs` + Responsibility: remember which parkour profile produced each node. +- Modify: `MinecraftClient/Pathing/Execution/PathSegment.cs` + Responsibility: expose per-segment `ParkourProfile`. +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` + Responsibility: thread `ParkourProfile` from planned node to runtime segment. +- Create: `MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs` + Responsibility: sidewall-specific admissibility with dominant-axis run-up and wall-adjacent arc rules. +- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` + Responsibility: shared helpers for dominant-axis runway checks, inside-wall depth validation, and sidewall landing clearance. +- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs` + Responsibility: register the full sidewall candidate set without disturbing current linear/cardinal move coverage. +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` + Responsibility: explicitly tag generic parkour as `ParkourProfile.Default`; do not change its linear admissibility rules. +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` + Responsibility: compute sidewall approach heading and dominant-axis progress. +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` + Responsibility: use the sidewall approach heading on the runway, then rotate toward the landing/exit heading in air without inducing turn stalls or replans. +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs` + Responsibility: exact in-memory world builder matching `tools/test-parkour.py::WorldBuilder.build_sidewall_route()`. +- Create: `MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs` + Responsibility: assert the in-memory builder matches live-harness geometry and endpoints. +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` + Responsibility: assert `ParkourProfile` survives path-to-segment translation. +- Create: `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` + Responsibility: direct planner admissibility tests for theory-allowed and theory-forbidden sidewall jumps. +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + Responsibility: exact live-coordinate sidewall planner regressions matching the harness. +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` + Responsibility: sidewall template convergence and no-spin regressions. +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + Responsibility: accepted sidewall chains complete with `0 replan`. +- Use for verification only: `tools/test-parkour.py` + Responsibility: parallel live matrix verification, not production code changes. + +### Task 1: Mirror The Live Sidewall Geometry In Test Fixtures + +**Files:** +- Create: `MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs` +- Create: `MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs` + +- [ ] **Step 1: Write the failing builder tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class SidewallParkourScenarioBuilderTests +{ + [Fact] + public void BuildWorld_FlatGap2Wo0_MatchesLiveRouteGeometry() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 98)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 99)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 102)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(100, 79, 101)).Type); + } + + [Fact] + public void BuildWorld_FlatGap3Wo1_ExtendsWallByTwoBlocksAlongRunwaySide() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 3, deltaY: 0, wallOffset: 1); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 101)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(99, 78, 102)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 103)).Type); + } + + [Fact] + public void Create_FlatGap2Wo0_UsesSameStartAndGoalAsLiveHarness() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + "sidewall-flat-gap2-wo0", + gap: 2, + deltaY: 0, + wallOffset: 0); + + Assert.Equal(new Location(100.5, 80, 100.5), scenario.Start); + Assert.Equal(97, scenario.Goal.X); + Assert.Equal(80, scenario.Goal.Y); + Assert.Equal(106, scenario.Goal.Z); + Assert.Equal(0f, scenario.StartYaw); + } +} +``` + +- [ ] **Step 2: Run the builder tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~SidewallParkourScenarioBuilderTests -v minimal +``` + +Expected: FAIL with missing-type errors for `SidewallParkourScenarioBuilder`. + +- [ ] **Step 3: Implement the exact sidewall scenario builder** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public static class SidewallParkourScenarioBuilder +{ + private const int SegmentCount = 3; + private const int BaseX = 100; + private const int BaseY = 80; + private const int BaseZ = 100; + private const int FloorY = BaseY - 1; + + public static IEnumerable AcceptedCases() + { + yield return ["sidewall-flat-gap2-wo0", 2, 0, 0]; + yield return ["sidewall-flat-gap3-wo1", 3, 0, 1]; + yield return ["sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0]; + yield return ["sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1]; + yield return ["sidewall-descend-gap2-dy-1-wo0", 2, -1, 0]; + yield return ["sidewall-descend-gap3-dy-1-wo1", 3, -1, 1]; + yield return ["sidewall-descend-gap2-dy-2-wo0", 2, -2, 0]; + yield return ["sidewall-descend-gap3-dy-2-wo1", 3, -2, 1]; + } + + public static IEnumerable RejectedCases() + { + yield return ["sidewall-flat-gap5-wo0", 5, 0, 0]; + yield return ["sidewall-flat-gap5-wo1", 5, 0, 1]; + yield return ["sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0]; + yield return ["sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1]; + yield return ["sidewall-descend-gap6-dy-1-wo0", 6, -1, 0]; + yield return ["sidewall-descend-gap6-dy-1-wo1", 6, -1, 1]; + yield return ["sidewall-descend-gap6-dy-2-wo0", 6, -2, 0]; + yield return ["sidewall-descend-gap6-dy-2-wo1", 6, -2, 1]; + } + + internal static PathingExecutionScenario Create(string scenarioId, int gap, int deltaY, int wallOffset, int maxExecutionTicks = 700) + { + int endFloorY = FloorY + (deltaY * SegmentCount); + int endX = BaseX - SegmentCount; + int endZ = BaseZ + (gap * SegmentCount); + + return new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = () => BuildWorld(gap, deltaY, wallOffset), + Start = new Location(BaseX + 0.5, BaseY, BaseZ + 0.5), + Goal = new GoalBlock(endX, endFloorY + 1, endZ), + StartYaw = 0f, + MaxExecutionTicks = maxExecutionTicks, + }; + } + + internal static World BuildWorld(int gap, int deltaY, int wallOffset) + { + int maxZ = BaseZ + gap * SegmentCount + 8; + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: 80, max: maxZ + 8); + FlatWorldTestBuilder.ClearBox(world, 90, 70, 90, 110, 96, maxZ + 8); + + int curX = BaseX; + int curY = FloorY; + int curZ = BaseZ; + + FlatWorldTestBuilder.FillSolid(world, curX, curY, curZ - 2, curX, curY, curZ); + + for (int segment = 0; segment < SegmentCount; segment++) + { + int wallX = curX - 1; + int wallZEnd = curZ + wallOffset; + int landX = curX - 1; + int landY = curY + deltaY; + int landZ = curZ + gap; + + FlatWorldTestBuilder.FillSolid( + world, + wallX, + Math.Min(curY, landY) - 1, + curZ, + wallX, + Math.Max(curY, landY) + 7, + wallZEnd); + FlatWorldTestBuilder.SetSolid(world, landX, landY, landZ); + + curX = landX; + curY = landY; + curZ = landZ; + } + + return world; + } +} +``` + +- [ ] **Step 4: Run the builder tests to verify they pass** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~SidewallParkourScenarioBuilderTests -v minimal +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs \ + MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs +git commit -m "test: add sidewall scenario builder fixtures" +``` + +--- + +### Task 2: Thread `ParkourProfile` From Planner Nodes To Runtime Segments + +**Files:** +- Create: `MinecraftClient/Pathing/Core/ParkourProfile.cs` +- Modify: `MinecraftClient/Pathing/Core/MoveResult.cs` +- Modify: `MinecraftClient/Pathing/Core/PathNode.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegment.cs` +- Modify: `MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs` + +- [ ] **Step 1: Write the failing profile-plumbing test** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs +[Fact] +public void FromPath_CopiesParkourProfile_ToRuntimeSegment() +{ + var start = new PathNode(100, 80, 100); + var end = new PathNode(99, 80, 102) + { + MoveUsed = MoveType.Parkour, + ParkourProfile = ParkourProfile.Sidewall + }; + + List segments = PathSegmentBuilder.FromPath([start, end]); + + Assert.Single(segments); + Assert.Equal(ParkourProfile.Sidewall, segments[0].ParkourProfile); +} +``` + +- [ ] **Step 2: Run the profile-plumbing test to verify it fails** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter FullyQualifiedName~FromPath_CopiesParkourProfile_ToRuntimeSegment -v minimal +``` + +Expected: FAIL with missing members such as `ParkourProfile` on `PathNode`, `MoveResult`, and `PathSegment`. + +- [ ] **Step 3: Add the profile enum and thread it through planner/runtime data structures** + +```csharp +// MinecraftClient/Pathing/Core/ParkourProfile.cs +namespace MinecraftClient.Pathing.Core +{ + public enum ParkourProfile + { + None = 0, + Default = 1, + Sidewall = 2 + } +} +``` + +```csharp +// MinecraftClient/Pathing/Core/MoveResult.cs +public struct MoveResult +{ + public int DestX; + public int DestY; + public int DestZ; + public double Cost; + public ParkourProfile ParkourProfile; + + public void Set(int x, int y, int z, double cost, ParkourProfile parkourProfile = ParkourProfile.None) + { + DestX = x; + DestY = y; + DestZ = z; + Cost = cost; + ParkourProfile = parkourProfile; + } + + public void SetImpossible() + { + Cost = ActionCosts.CostInf; + ParkourProfile = ParkourProfile.None; + } +} +``` + +```csharp +// MinecraftClient/Pathing/Core/PathNode.cs +public sealed class PathNode +{ + // existing fields... + public MoveType MoveUsed; + public ParkourProfile ParkourProfile; +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegment.cs +public sealed class PathSegment +{ + public required Location Start { get; init; } + public required Location End { get; init; } + public required MoveType MoveType { get; init; } + public ParkourProfile ParkourProfile { get; init; } = ParkourProfile.None; + // existing properties... +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs +private static PathSegment CreatePreview(PathNode start, PathNode end) +{ + return new PathSegment + { + Start = new Location(start.X + 0.5, start.Y, start.Z + 0.5), + End = new Location(end.X + 0.5, end.Y, end.Z + 0.5), + MoveType = end.MoveUsed, + ParkourProfile = end.ParkourProfile + }; +} +``` + +```csharp +// MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs +result.Set(destX, destY, destZ, cost, ParkourProfile.Default); +``` + +- [ ] **Step 4: Run the profile-plumbing test to verify it passes** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~FromPath_CopiesParkourProfile_ToRuntimeSegment|FullyQualifiedName~FromPath_AnnotatesTraverseIntoParkour_AsPrepareJump" -v minimal +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient/Pathing/Core/ParkourProfile.cs \ + MinecraftClient/Pathing/Core/MoveResult.cs \ + MinecraftClient/Pathing/Core/PathNode.cs \ + MinecraftClient/Pathing/Execution/PathSegment.cs \ + MinecraftClient/Pathing/Execution/PathSegmentBuilder.cs \ + MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs \ + MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs +git commit -m "refactor: thread parkour profile into runtime segments" +``` + +--- + +### Task 3: Implement Planner Support For Sidewall Parkour + +**Files:** +- Create: `MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs` +- Create: `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` +- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` +- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + +- [ ] **Step 1: Write the failing planner tests for exact theory-allowed and theory-forbidden cases** + +```csharp +// MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; +using MinecraftClient.Pathing.Moves.Impl; +using MinecraftClient.Tests.Pathing.Execution; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Moves; + +public sealed class MoveSidewallParkourTests +{ + [Theory] + [InlineData("sidewall-flat-gap2-wo0", 2, 0, 0)] + [InlineData("sidewall-flat-gap3-wo1", 3, 0, 1)] + [InlineData("sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0)] + [InlineData("sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1)] + [InlineData("sidewall-descend-gap2-dy-1-wo0", 2, -1, 0)] + [InlineData("sidewall-descend-gap3-dy-1-wo1", 3, -1, 1)] + [InlineData("sidewall-descend-gap2-dy-2-wo0", 2, -2, 0)] + [InlineData("sidewall-descend-gap3-dy-2-wo1", 3, -2, 1)] + public void Calculate_AcceptsTheoryAllowedCases(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveSidewallParkour(xOffset: -1, zOffset: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.False(result.IsImpossible); + Assert.Equal(ParkourProfile.Sidewall, result.ParkourProfile); + } + + [Theory] + [InlineData("sidewall-flat-gap5-wo0", 5, 0, 0)] + [InlineData("sidewall-flat-gap5-wo1", 5, 0, 1)] + [InlineData("sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0)] + [InlineData("sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1)] + [InlineData("sidewall-descend-gap6-dy-1-wo0", 6, -1, 0)] + [InlineData("sidewall-descend-gap6-dy-2-wo1", 6, -2, 1)] + public void Calculate_RejectsTheoryForbiddenCases(string scenarioId, int gap, int deltaY, int wallOffset) + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveSidewallParkour(xOffset: -1, zOffset: gap, yDelta: deltaY); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.True(result.IsImpossible); + } +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +[Theory] +[MemberData(nameof(SidewallParkourScenarioBuilder.AcceptedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] +public void AStar_SidewallAcceptedCases_PlanThroughAllThreeJumps(string scenarioId, int gap, int deltaY, int wallOffset) +{ + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.Equal(3, segments.FindAll(segment => segment.MoveType == MoveType.Parkour).Count); + Assert.All(segments, segment => + { + if (segment.MoveType == MoveType.Parkour) + Assert.Equal(ParkourProfile.Sidewall, segment.ParkourProfile); + }); + Assert.Equal(scenario.Goal.X + 0.5, segments[^1].End.X); + Assert.Equal(scenario.Goal.Y, segments[^1].End.Y); + Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z); +} + +[Theory] +[MemberData(nameof(SidewallParkourScenarioBuilder.RejectedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] +public void AStar_SidewallRejectedCases_RejectBeforeExecution(string scenarioId, int gap, int deltaY, int wallOffset) +{ + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + + Assert.Equal(PathStatus.Failed, result.Status); + Assert.Empty(PathSegmentBuilder.FromPath(result.Path)); +} +``` + +- [ ] **Step 2: Run the planner tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~AStar_Sidewall" -v minimal +``` + +Expected: FAIL because `MoveSidewallParkour` does not exist yet and the planner currently has no sidewall candidate family. + +- [ ] **Step 3: Implement a dedicated sidewall move and register the full candidate table** + +```csharp +// MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + public sealed class MoveSidewallParkour : IMove + { + public MoveType Type => MoveType.Parkour; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => false; + + private readonly int _yDelta; + + public MoveSidewallParkour(int xOffset, int zOffset, int yDelta = 0) + { + XOffset = xOffset; + ZOffset = zOffset; + _yDelta = yDelta; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + if (!ctx.AllowParkour || !ctx.CanSprint) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.IsSidewallProfile(XOffset, ZOffset, _yDelta)) + { + result.SetImpossible(); + return; + } + + ParkourFeasibility.GetSidewallAxes(XOffset, ZOffset, out int forwardX, out int forwardZ, out int lateralX, out int lateralZ); + + int destX = x + XOffset; + int destY = y + _yDelta; + int destZ = z + ZOffset; + + if (!ctx.CanWalkThrough(x, y + 2, z)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasDominantAxisRunUp(ctx, x, y, z, forwardX, forwardZ, XOffset, ZOffset, _yDelta)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasSidewallArcClearance(ctx, x, y, z, forwardX, forwardZ, lateralX, lateralZ, XOffset, ZOffset, _yDelta)) + { + result.SetImpossible(); + return; + } + + if (!ParkourFeasibility.HasSidewallLandingClearance(ctx, destX, destY, destZ, forwardX, forwardZ, lateralX, lateralZ)) + { + result.SetImpossible(); + return; + } + + double horizDist = Math.Sqrt((double)(XOffset * XOffset + ZOffset * ZOffset)); + double cost = _yDelta switch + { + > 0 => horizDist * ctx.SprintCost + ctx.JumpPenalty * 2, + < 0 => horizDist * ctx.SprintCost + ctx.JumpPenalty + ActionCosts.FallCost(-_yDelta), + _ => horizDist * ctx.SprintCost + ctx.JumpPenalty, + }; + + result.Set(destX, destY, destZ, cost, ParkourProfile.Sidewall); + } + } +} +``` + +```csharp +// MinecraftClient/Pathing/Moves/ParkourFeasibility.cs +internal static bool IsSidewallProfile(int xOffset, int zOffset, int yDelta) +{ + int absX = Math.Abs(xOffset); + int absZ = Math.Abs(zOffset); + int major = Math.Max(absX, absZ); + int minor = Math.Min(absX, absZ); + + return minor == 1 + && major >= 2 + && major <= 5 + && yDelta is >= -2 and <= 1; +} + +internal static void GetSidewallAxes(int xOffset, int zOffset, out int forwardX, out int forwardZ, out int lateralX, out int lateralZ) +{ + if (Math.Abs(xOffset) > Math.Abs(zOffset)) + { + forwardX = Math.Sign(xOffset); + forwardZ = 0; + lateralX = 0; + lateralZ = Math.Sign(zOffset); + } + else + { + forwardX = 0; + forwardZ = Math.Sign(zOffset); + lateralX = Math.Sign(xOffset); + lateralZ = 0; + } +} + +internal static bool HasDominantAxisRunUp(CalculationContext ctx, int x, int y, int z, int forwardX, int forwardZ, int xOffset, int zOffset, int yDelta) +{ + int requiredBlocks = yDelta switch + { + > 0 => 2, + < 0 when Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)) >= 5 => 2, + < 0 => 1, + _ when Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)) >= 4 => 2, + _ => 1, + }; + + for (int i = 1; i <= requiredBlocks; i++) + { + int rx = x - forwardX * i; + int rz = z - forwardZ * i; + if (!ctx.CanWalkOn(rx, y - 1, rz) || !IsColumnPassable(ctx, rx, y, rz)) + return false; + } + + return true; +} + +internal static bool HasSidewallArcClearance(CalculationContext ctx, int x, int y, int z, int forwardX, int forwardZ, int lateralX, int lateralZ, int xOffset, int zOffset, int yDelta) +{ + int major = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + int insideWallDepth = 0; + + for (int step = 0; step < 2; step++) + { + int wx = x + lateralX + (forwardX * step); + int wz = z + lateralZ + (forwardZ * step); + if (ctx.CanWalkThrough(wx, y, wz) && ctx.CanWalkThrough(wx, y + 1, wz)) + break; + insideWallDepth++; + } + + if (insideWallDepth is < 1 or > 2) + return false; + + for (int step = 1; step <= major; step++) + { + int cx = x + (forwardX * step); + int cz = z + (forwardZ * step); + if (!IsColumnPassable(ctx, cx, y, cz)) + return false; + } + + int outsideX = x - lateralX; + int outsideZ = z - lateralZ; + return IsColumnPassable(ctx, outsideX, y, outsideZ); +} + +internal static bool HasSidewallLandingClearance(CalculationContext ctx, int destX, int destY, int destZ, int forwardX, int forwardZ, int lateralX, int lateralZ) +{ + if (!ctx.CanWalkOn(destX, destY - 1, destZ)) + return false; + + if (!IsColumnPassable(ctx, destX, destY, destZ)) + return false; + + if (!IsColumnPassable(ctx, destX + forwardX, destY, destZ + forwardZ)) + return false; + + if (!IsColumnPassable(ctx, destX - lateralX, destY, destZ - lateralZ)) + return false; + + return true; +} +``` + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +foreach (int dx in offsets) +{ + foreach (int dz in offsets) + { + foreach (int distance in new[] { 2, 3, 4, 5 }) + { + moves.Add(new MoveSidewallParkour(dx, dz * distance)); + moves.Add(new MoveSidewallParkour(dx * distance, dz)); + + if (distance <= 3) + { + moves.Add(new MoveSidewallParkour(dx, dz * distance, yDelta: 1)); + moves.Add(new MoveSidewallParkour(dx * distance, dz, yDelta: 1)); + } + + moves.Add(new MoveSidewallParkour(dx, dz * distance, yDelta: -1)); + moves.Add(new MoveSidewallParkour(dx * distance, dz, yDelta: -1)); + moves.Add(new MoveSidewallParkour(dx, dz * distance, yDelta: -2)); + moves.Add(new MoveSidewallParkour(dx * distance, dz, yDelta: -2)); + } + } +} +``` + +- [ ] **Step 4: Run the planner tests to verify they pass** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~AStar_Sidewall" -v minimal +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs \ + MinecraftClient/Pathing/Moves/ParkourFeasibility.cs \ + MinecraftClient/Pathing/Core/AStarPathFinder.cs \ + MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs \ + MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +git commit -m "feat: add sidewall parkour planner support" +``` + +--- + +### Task 4: Teach The Executor To Take Sidewall Jumps Without Replan Or Spin + +**Files:** +- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs` +- Modify: `MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + +- [ ] **Step 1: Write the failing execution tests** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs +[Fact] +public void SprintJumpTemplate_SidewallFlatGap2_FinalStop_CompletesInsideLandingBlock() +{ + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0); + var segment = new PathSegment + { + Start = new Location(100.5, 80, 100.5), + End = new Location(99.5, 80, 102.5), + MoveType = MoveType.Parkour, + ParkourProfile = ParkourProfile.Sidewall, + ExitTransition = PathTransitionType.FinalStop + }; + + var template = new SprintJumpTemplate(segment, null); + var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f); + + TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 120, out Location finalPos); + + Assert.Equal(TemplateState.Complete, state); + Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End)); +} +``` + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs +[Theory] +[MemberData(nameof(SidewallParkourScenarioBuilder.AcceptedCases), MemberType = typeof(SidewallParkourScenarioBuilder))] +public void Tick_SidewallAcceptedCases_CompletesWithoutReplan(string scenarioId, int gap, int deltaY, int wallOffset) +{ + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + + Assert.True( + result.Completed && result.ReplanCount == 0, + $"scenario={scenarioId} completed={result.Completed} replans={result.ReplanCount} final={result.FinalPosition}\n" + + $"{string.Join('\n', result.InfoLogs)}\n{string.Join('\n', result.DebugLogs)}"); +} +``` + +- [ ] **Step 2: Run the execution tests to verify they fail** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplate_Sidewall|FullyQualifiedName~Tick_SidewallAcceptedCases" -v minimal +``` + +Expected: FAIL because the current template turns toward the landing yaw before it has built runway momentum, which either stalls in place or forces a rescue replan after a bad takeoff. + +- [ ] **Step 3: Use `ParkourProfile.Sidewall` to separate runway heading from landing heading** + +```csharp +// MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +internal static void GetApproachHeading(PathSegment segment, out int headingX, out int headingZ) +{ + if (segment.ParkourProfile == ParkourProfile.Sidewall) + { + double dx = Math.Abs(segment.End.X - segment.Start.X); + double dz = Math.Abs(segment.End.Z - segment.Start.Z); + + if (dx > dz) + { + headingX = segment.HeadingX; + headingZ = 0; + } + else + { + headingX = 0; + headingZ = segment.HeadingZ; + } + + return; + } + + headingX = segment.HeadingX; + headingZ = segment.HeadingZ; +} + +internal static float GetApproachYaw(PathSegment segment) +{ + GetApproachHeading(segment, out int headingX, out int headingZ); + return CalculateYaw(headingX, headingZ); +} + +internal static double ProgressAlongApproach(Location start, Location pos, PathSegment segment) +{ + GetApproachHeading(segment, out int headingX, out int headingZ); + return ((pos.X - start.X) * headingX) + ((pos.Z - start.Z) * headingZ); +} +``` + +```csharp +// MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +float approachYaw = TemplateHelper.GetApproachYaw(_segment); +float activeYaw = _phase == Phase.Approach ? approachYaw : targetYaw; + +physics.Yaw = groundedPrepareJumpHandoff + ? TemplateHelper.SmoothYaw(physics.Yaw, TemplateHelper.GetExitHeadingYaw(_segment)) + : TemplateHelper.SmoothYaw(physics.Yaw, activeYaw); + +case Phase.Approach: + if (physics.OnGround) + { + double approachProgress = TemplateHelper.ProgressAlongApproach(ExpectedStart, pos, _segment); + float yawDelta = YawDifference(physics.Yaw, approachYaw); + bool turnInPlace = yawDelta > 35f; + input.Forward = !turnInPlace; + input.Sprint = !turnInPlace; + + double minApproachDistance = _segment.ParkourProfile == ParkourProfile.Sidewall + ? 0.9 + : _horizDist >= 5.0 ? 0.8 + : _horizDist >= 4.0 ? 0.6 + : _horizDist > 3.5 ? 0.3 + : 0.0; + + if (yawDelta < YawToleranceDeg && approachProgress >= minApproachDistance) + { + input.Jump = true; + _phase = Phase.Airborne; + } + } + break; + +case Phase.Airborne: + if (_segment.ParkourProfile == ParkourProfile.Sidewall && _leftGround) + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw, maxStep: 20f); + break; +``` + +- [ ] **Step 4: Run the execution tests to verify they pass** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SprintJumpTemplate_Sidewall|FullyQualifiedName~Tick_SidewallAcceptedCases" -v minimal +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs \ + MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs \ + MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs \ + MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs +git commit -m "feat: execute sidewall parkour without replan" +``` + +--- + +### Task 5: Verify Sidewall Live Matrix And Protect Linear + +**Files:** +- Test: `MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/PathSegmentBuilderTests.cs` +- Test: `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs` +- Test: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + +- [ ] **Step 1: Run the targeted .NET sidewall regression suite** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~SidewallParkourScenarioBuilderTests|FullyQualifiedName~FromPath_CopiesParkourProfile_ToRuntimeSegment|FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~AStar_Sidewall|FullyQualifiedName~SprintJumpTemplate_Sidewall|FullyQualifiedName~Tick_SidewallAcceptedCases" -v minimal +``` + +Expected: PASS. + +- [ ] **Step 2: Run the full sidewall live matrix in parallel** + +Run: + +```bash +source tools/mcc-env.sh && python3 tools/test-parkour.py --filter sidewall --parallel 6 --version 1.21.11-Vanilla --results /tmp/sidewall-parkour-final.jsonl +``` + +Expected: summary reports `30/30 matched expectations` and `0 cases skipped`. + +- [ ] **Step 3: Prove the sidewall JSONL has zero replan and zero turn-stall on accepted cases** + +Run: + +```bash +python3 - <<'PY' +import json +from pathlib import Path + +rows = [json.loads(line) for line in Path('/tmp/sidewall-parkour-final.jsonl').read_text().splitlines() if line.strip()] +assert len(rows) == 30, len(rows) +assert sum(1 for row in rows if row["matched"]) == 30 +assert all( + row["outcome"] != "pass" or (row["replan_count"] == 0 and row["turn_stall_count"] == 0) + for row in rows +) +print("rows", len(rows)) +print("matched", sum(1 for row in rows if row["matched"])) +print("pass_cases", sum(1 for row in rows if row["outcome"] == "pass")) +print("reject_cases", sum(1 for row in rows if row["outcome"] == "reject")) +PY +``` + +Expected: + +```text +rows 30 +matched 30 +pass_cases 22 +reject_cases 8 +``` + +- [ ] **Step 4: Re-run the linear live matrix as a hard regression guard** + +Run: + +```bash +source tools/mcc-env.sh && python3 tools/test-parkour.py --filter linear --parallel 6 --version 1.21.11-Vanilla --results /tmp/linear-guard-after-sidewall.jsonl +python3 - <<'PY' +import json +from pathlib import Path + +rows = [json.loads(line) for line in Path('/tmp/linear-guard-after-sidewall.jsonl').read_text().splitlines() if line.strip()] +assert len(rows) == 22, len(rows) +assert sum(1 for row in rows if row["matched"]) == 22 +assert all( + row["outcome"] != "pass" or (row["replan_count"] == 0 and row["turn_stall_count"] == 0) + for row in rows +) +print("rows", len(rows)) +print("matched", sum(1 for row in rows if row["matched"])) +print("pass_cases", sum(1 for row in rows if row["outcome"] == "pass")) +print("reject_cases", sum(1 for row in rows if row["outcome"] == "reject")) +PY +``` + +Expected: + +```text +rows 22 +matched 22 +pass_cases 18 +reject_cases 4 +``` + +- [ ] **Step 5: Re-run the existing green linear .NET regressions** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Tick_Linear|FullyQualifiedName~AStar_Linear|FullyQualifiedName~PathSegmentManager_LiveCoordinateLinear" -v minimal +``` + +Expected: PASS. + +## Self-Review + +**1. Spec coverage** + +- Sidewall pass coverage: Task 3 adds exact theory-allowed and theory-forbidden planner tests; Task 4 adds executor no-replan tests; Task 5 runs the full `sidewall` live matrix. +- Zero replan / zero turn stall: Task 4 enforces `PathSegmentManager` no-replan behavior; Task 5 validates JSONL `replan_count` and `turn_stall_count`. +- Parallel verification through `tools/test-parkour.py`: Task 5 uses `--parallel 6`. +- Preserve linear: Task 5 re-runs both live `linear` matrix and existing green linear .NET regressions. +- Excluding `neo` and `ceiling`: called out explicitly in Scope And Guardrails. + +**2. Placeholder scan** + +- No `TODO`, `TBD`, or “similar to above” placeholders remain. +- Every task lists exact file paths, concrete test names, explicit commands, and concrete code identifiers. + +**3. Type consistency** + +- `ParkourProfile` is the single profile type threaded across `MoveResult`, `PathNode`, and `PathSegment`. +- `MoveSidewallParkour` is the dedicated planner type; generic `MoveParkour` remains tagged as `ParkourProfile.Default`. +- `SidewallParkourScenarioBuilder` is the shared fixture source used by move tests, live planner tests, and manager tests. diff --git a/docs/superpowers/plans/2026-04-19-sidewall-runup-precondition-plan.md b/docs/superpowers/plans/2026-04-19-sidewall-runup-precondition-plan.md new file mode 100644 index 0000000000..efe977adf3 --- /dev/null +++ b/docs/superpowers/plans/2026-04-19-sidewall-runup-precondition-plan.md @@ -0,0 +1,596 @@ +# Sidewall Runup Precondition Implementation Plan + +I'm using the writing-plans skill to create the 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:** Make the remaining static-entry sidewall long-descend jumps plan through an explicit backward-then-forward runway setup, while preserving the current all-green `linear` behavior and keeping accepted executions at `replan_count=0` and `turn_stall_count=0`. + +**Architecture:** Extend A* with a narrow `EntryPreparationState` keyed into the search node identity, so the pathfinder can distinguish “standing on the launch block unprepared” from “standing on the same block after completing a setup runway.” Keep the resulting path explicit by composing the setup out of ordinary `Traverse` segments, and gate `MoveSidewallParkour` on the prepared state only for the narrow profile that currently needs extra entry momentum. + +**Tech Stack:** .NET 10 / C# 14, xUnit, MCC pathing core, `tools/test-parkour.py`, local `1.21.11-Vanilla` live harness + +--- + +## Scope And Guardrails + +- Phase 1 activation is intentionally narrow: static-entry `sidewall`, `yDelta == -1`, dominant distance `== 5`, no carry-in. +- Do not edit `MoveParkour` or any generic linear admissibility logic in this plan. +- Do not touch `SprintJumpTemplate` or `SidewallParkourController` in the first pass. If the new explicit runway path exposes a runtime issue later, stop and write a follow-up plan instead of silently expanding scope. +- The workspace is already dirty. Do not reset, discard, or overwrite unrelated changes. Save new docs under `2026-04-19-*` filenames instead of modifying the existing untracked `2026-04-18` sidewall plan. +- The verification gate for this plan is targeted pathing tests plus the live harness, not the full `MinecraftClient.Tests` suite, because the baseline still contains unrelated failures. + +## File Structure + +- Create: `MinecraftClient/Pathing/Core/EntryPreparationKind.cs` + Responsibility: enum describing whether a node has no setup state or a sidewall runup state. +- Create: `MinecraftClient/Pathing/Core/EntryPreparationState.cs` + Responsibility: immutable value object that records launch origin, dominant axis, required steps, and progress through backward/return phases. +- Modify: `MinecraftClient/Pathing/Core/PathNode.cs` + Responsibility: store `EntryPreparationState` on each node. +- Modify: `MinecraftClient/Pathing/Core/CalculationContext.cs` + Responsibility: expose the current node’s `EntryPreparationState` to move feasibility. +- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs` + Responsibility: key nodes by position plus preparation state, seed and advance sidewall runup setup, and preserve explicit traverse segments in the planned path. +- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` + Responsibility: classify when a sidewall profile requires setup and validate a prepared setup against launch origin and dominant axis. +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs` + Responsibility: reject the narrow long-descend static-entry sidewall profile unless the current node carries a matching prepared setup state. +- Modify: `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` + Responsibility: direct admissibility guard for long-descend sidewall static entry. +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + Responsibility: assert the planner emits explicit setup traverses for the long-descend sidewall profile and does not inject setup into linear routes. +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + Responsibility: ensure the planned sidewall setup path still executes at `0 replan`. +- Use for verification only: `tools/test-parkour.py` + Responsibility: live-harness acceptance, not production code changes. + +### Task 1: Freeze The Required Planner Shape In Tests + +**Files:** +- Modify: `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` +- Modify: `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` + +- [ ] **Step 1: Add a direct move test that proves long-descend sidewall static entry is not directly admissible** + +```csharp +// MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs +[Theory] +[InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)] +[InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)] +public void Calculate_LongDescendStaticEntry_RejectsWithoutPreparedRunup( + string scenarioId, + int gap, + int wallOffset) +{ + World world = SidewallParkourScenarioBuilder.BuildWorld(gap, deltaY: -1, wallOffset); + var ctx = new CalculationContext(world, allowParkour: true, allowParkourAscend: true); + var move = new MoveSidewallParkour(xOffset: -1, zOffset: gap, yDelta: -1); + MoveResult result = default; + + move.Calculate(ctx, 100, 80, 100, ref result); + + Assert.True(result.IsImpossible, scenarioId); +} +``` + +- [ ] **Step 2: Add a planner regression that requires explicit setup traverses before the first sidewall jump** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +[Theory] +[InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)] +[InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)] +public void AStar_SidewallLongDescendStaticEntry_PrependsExplicitRunupTraverses( + string scenarioId, + int gap, + int wallOffset) +{ + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + scenarioId, + gap, + deltaY: -1, + wallOffset); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + + int firstParkourIndex = segments.FindIndex(segment => segment.MoveType == MoveType.Parkour); + Assert.True(firstParkourIndex >= 4, string.Join('\n', segments)); + Assert.All( + segments.Take(firstParkourIndex), + segment => Assert.Equal(MoveType.Traverse, segment.MoveType)); + Assert.Equal(new Location(100.5, 80, 100.5), segments[firstParkourIndex - 1].End); + Assert.Equal(ParkourProfile.Sidewall, segments[firstParkourIndex].ParkourProfile); +} +``` + +- [ ] **Step 3: Add a linear regression that proves no setup is injected into an already-green route** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs +[Fact] +public void AStar_LinearFlatGap4_DoesNotInsertRunupSetupSegments() +{ + PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0); + PathResult result = PathingScenarioRunner.PlanOnly(scenario); + List segments = PathSegmentBuilder.FromPath(result.Path); + + Assert.Equal(PathStatus.Success, result.Status); + Assert.NotEmpty(segments); + Assert.Equal(MoveType.Parkour, segments[0].MoveType); +} +``` + +- [ ] **Step 4: Run the focused planner tests and verify they fail before implementation** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: + +- `Calculate_LongDescendStaticEntry_RejectsWithoutPreparedRunup` fails because the move still admits the jump directly. +- `AStar_SidewallLongDescendStaticEntry_PrependsExplicitRunupTraverses` fails because the planner still tries to jump from a plain origin node. + +### Task 2: Add Search-State Types For Planner-Side Setup + +**Files:** +- Create: `MinecraftClient/Pathing/Core/EntryPreparationKind.cs` +- Create: `MinecraftClient/Pathing/Core/EntryPreparationState.cs` +- Modify: `MinecraftClient/Pathing/Core/PathNode.cs` +- Modify: `MinecraftClient/Pathing/Core/CalculationContext.cs` + +- [ ] **Step 1: Add the preparation-kind enum** + +```csharp +// MinecraftClient/Pathing/Core/EntryPreparationKind.cs +namespace MinecraftClient.Pathing.Core +{ + public enum EntryPreparationKind + { + None = 0, + SidewallRunup = 1 + } +} +``` + +- [ ] **Step 2: Add the immutable preparation-state value object** + +```csharp +// MinecraftClient/Pathing/Core/EntryPreparationState.cs +namespace MinecraftClient.Pathing.Core +{ + public readonly record struct EntryPreparationState( + EntryPreparationKind Kind, + int OriginX, + int OriginY, + int OriginZ, + int ForwardX, + int ForwardZ, + byte RequiredSteps, + byte BackwardSteps, + byte ReturnSteps) + { + public static EntryPreparationState None => default; + + public bool IsNone => Kind == EntryPreparationKind.None; + + public bool IsPrepared => + Kind != EntryPreparationKind.None && + BackwardSteps == RequiredSteps && + ReturnSteps == RequiredSteps; + + public EntryPreparationState AdvanceBackward() => + this with { BackwardSteps = (byte)(BackwardSteps + 1) }; + + public EntryPreparationState AdvanceReturn() => + this with { ReturnSteps = (byte)(ReturnSteps + 1) }; + } +} +``` + +- [ ] **Step 3: Thread the state through path nodes and calculation context** + +```csharp +// MinecraftClient/Pathing/Core/PathNode.cs +public EntryPreparationState EntryPreparation; + +// MinecraftClient/Pathing/Core/CalculationContext.cs +public EntryPreparationState CurrentEntryPreparation { get; internal set; } +``` + +- [ ] **Step 4: Run a compile-only build to catch signature issues early** + +Run: + +```bash +dotnet build MinecraftClient.sln -c Debug +``` + +Expected: + +- The build fails in `AStarPathFinder` and `MoveSidewallParkour` because the new state has not been wired into expansion logic yet. + +### Task 3: Key A* By Position Plus Preparation State And Advance The Setup Path + +**Files:** +- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs` + +- [ ] **Step 1: Add a search-key type inside `AStarPathFinder` and stop deduplicating by packed position alone** + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +private readonly record struct NodeKey(long PackedPosition, EntryPreparationState EntryPreparation); + +// replace +var nodeMap = new Dictionary(4096); + +// replace start-node insertion +nodeMap[new NodeKey(startNode.PackedPosition, startNode.EntryPreparation)] = startNode; +``` + +- [ ] **Step 2: Push the current node state into the calculation context before each move expansion** + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +foreach (var move in _allMoves) +{ + ctx.PreviousMoveType = current.MoveUsed; + ctx.CurrentEntryPreparation = current.EntryPreparation; + moveResult.Cost = 0; + move.Calculate(ctx, current.X, current.Y, current.Z, ref moveResult); + // ... +} +``` + +- [ ] **Step 3: Add a helper that seeds, advances, or clears the setup state using only ordinary `Traverse` moves** + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +private EntryPreparationState ResolveEntryPreparation(PathNode current, IMove move, in MoveResult moveResult) +{ + EntryPreparationState advanced = AdvanceExistingPreparation(current, move, moveResult); + if (!advanced.IsNone) + return advanced; + + if (TryStartSidewallRunupPreparation(current, move, moveResult, out EntryPreparationState started)) + return started; + + return EntryPreparationState.None; +} + +private static EntryPreparationState AdvanceExistingPreparation(PathNode current, IMove move, in MoveResult moveResult) +{ + EntryPreparationState state = current.EntryPreparation; + if (state.IsNone) + return EntryPreparationState.None; + + if (move.Type != MoveType.Traverse || moveResult.DestY != current.Y) + return EntryPreparationState.None; + + int stepX = moveResult.DestX - current.X; + int stepZ = moveResult.DestZ - current.Z; + + if (state.BackwardSteps < state.RequiredSteps && + stepX == -state.ForwardX && + stepZ == -state.ForwardZ) + { + return state.AdvanceBackward(); + } + + if (state.BackwardSteps == state.RequiredSteps && + state.ReturnSteps < state.RequiredSteps && + stepX == state.ForwardX && + stepZ == state.ForwardZ) + { + return state.AdvanceReturn(); + } + + return EntryPreparationState.None; +} +``` + +- [ ] **Step 4: Seed the setup state only when a one-block backward traverse matches a setup-required sidewall profile from the current origin** + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +private bool TryStartSidewallRunupPreparation( + PathNode current, + IMove move, + in MoveResult moveResult, + out EntryPreparationState state) +{ + state = EntryPreparationState.None; + + if (current.EntryPreparation.Kind != EntryPreparationKind.None || + move.Type != MoveType.Traverse || + moveResult.DestY != current.Y) + { + return false; + } + + int stepX = moveResult.DestX - current.X; + int stepZ = moveResult.DestZ - current.Z; + + foreach (MoveSidewallParkour sidewallMove in _allMoves.OfType()) + { + if (!ParkourFeasibility.TryGetRequiredStaticEntryRunupSteps( + current.MoveUsed, + sidewallMove.XOffset, + sidewallMove.ZOffset, + sidewallMove.YDelta, + out int requiredSteps)) + { + continue; + } + + ParkourFeasibility.GetSidewallAxes( + sidewallMove.XOffset, + sidewallMove.ZOffset, + out int forwardX, + out int forwardZ, + out _, + out _); + + if (stepX == -forwardX && stepZ == -forwardZ) + { + state = new EntryPreparationState( + EntryPreparationKind.SidewallRunup, + current.X, + current.Y, + current.Z, + forwardX, + forwardZ, + (byte)requiredSteps, + BackwardSteps: 1, + ReturnSteps: 0); + return true; + } + } + + return false; +} +``` + +- [ ] **Step 5: Attach the resolved state to the neighbor before node lookup and update the node-map key** + +```csharp +// MinecraftClient/Pathing/Core/AStarPathFinder.cs +EntryPreparationState nextPreparation = ResolveEntryPreparation(current, move, moveResult); +var key = new NodeKey(PathNode.Pack(nx, ny, nz), nextPreparation); + +if (nodeMap.TryGetValue(key, out var neighbor)) +{ + // existing better-path update + neighbor.EntryPreparation = nextPreparation; +} +else +{ + neighbor = new PathNode(nx, ny, nz) + { + GCost = tentativeG, + HCost = goal.Heuristic(nx, ny, nz), + Parent = current, + MoveUsed = move.Type, + ParkourProfile = moveResult.ParkourProfile, + EntryPreparation = nextPreparation, + IsOpen = true + }; + nodeMap[key] = neighbor; + openSet.Insert(neighbor); +} +``` + +- [ ] **Step 6: Run the focused planner tests again** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: + +- The compile-time errors are gone. +- The planner-shape test still fails until `MoveSidewallParkour` recognizes the prepared state. + +### Task 4: Gate The Narrow Sidewall Profile On Prepared Setup + +**Files:** +- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs` +- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs` + +- [ ] **Step 1: Add a helper that classifies setup-required sidewall profiles** + +```csharp +// MinecraftClient/Pathing/Moves/ParkourFeasibility.cs +public static bool TryGetRequiredStaticEntryRunupSteps( + MoveType previousMoveType, + int xOffset, + int zOffset, + int yDelta, + out int requiredSteps) +{ + requiredSteps = 0; + + if (previousMoveType is MoveType.Parkour or MoveType.Descend) + return false; + + int major = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset)); + if (yDelta == -1 && major == 5) + { + requiredSteps = 2; + return true; + } + + return false; +} +``` + +- [ ] **Step 2: Add a helper that validates a prepared setup against the exact launch origin and dominant axis** + +```csharp +// MinecraftClient/Pathing/Moves/ParkourFeasibility.cs +public static bool HasPreparedRunup( + EntryPreparationState state, + int x, + int y, + int z, + int forwardX, + int forwardZ, + int requiredSteps) +{ + return state.Kind == EntryPreparationKind.SidewallRunup && + state.IsPrepared && + state.OriginX == x && + state.OriginY == y && + state.OriginZ == z && + state.ForwardX == forwardX && + state.ForwardZ == forwardZ && + state.RequiredSteps == requiredSteps; +} +``` + +- [ ] **Step 3: Expose `YDelta` from `MoveSidewallParkour` so the pathfinder can inspect sidewall candidates** + +```csharp +// MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs +public int YDelta => _yDelta; +``` + +- [ ] **Step 4: Reject the narrow static-entry profile unless the current node carries a matching prepared setup** + +```csharp +// MinecraftClient/Pathing/Moves/Impl/MoveSidewallParkour.cs +ParkourFeasibility.GetSidewallAxes(XOffset, ZOffset, out int forwardX, out int forwardZ, out int lateralX, out int lateralZ); + +if (ParkourFeasibility.TryGetRequiredStaticEntryRunupSteps( + ctx.PreviousMoveType, + XOffset, + ZOffset, + _yDelta, + out int requiredSteps)) +{ + if (!ParkourFeasibility.HasPreparedRunup( + ctx.CurrentEntryPreparation, + x, + y, + z, + forwardX, + forwardZ, + requiredSteps)) + { + result.SetImpossible(); + return; + } +} +else if (!ParkourFeasibility.HasDominantAxisRunUp(ctx, x, y, z, forwardX, forwardZ, XOffset, ZOffset, _yDelta)) +{ + result.SetImpossible(); + return; +} +``` + +- [ ] **Step 5: Run the focused planner tests and verify they now pass** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~LivePathingRegressionTests" -v minimal +``` + +Expected: + +- `Calculate_LongDescendStaticEntry_RejectsWithoutPreparedRunup` passes. +- `AStar_SidewallLongDescendStaticEntry_PrependsExplicitRunupTraverses` passes. +- `AStar_LinearFlatGap4_DoesNotInsertRunupSetupSegments` passes. + +### Task 5: Prove The New Path Still Executes At Zero Replan + +**Files:** +- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + +- [ ] **Step 1: Add a focused execution regression for the two long-descend sidewall profiles** + +```csharp +// MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs +[Theory] +[InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)] +[InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)] +public void Tick_SidewallLongDescendRunupSetup_CompletesWithoutReplan( + string scenarioId, + int gap, + int wallOffset) +{ + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + scenarioId, + gap, + deltaY: -1, + wallOffset); + PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario); + Location goalLocation = new(scenario.Goal.X + 0.5, scenario.Goal.Y, scenario.Goal.Z + 0.5); + + Assert.True( + result.Completed && + result.ReplanCount == 0 && + TemplateFootingHelper.IsFootprintInsideTargetBlock(result.FinalPosition, goalLocation), + $"scenario={scenarioId} completed={result.Completed} replans={result.ReplanCount} final={result.FinalPosition}\n" + + $"info={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}"); +} +``` + +- [ ] **Step 2: Run the targeted execution tests** + +Run: + +```bash +dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~PathSegmentManagerTests|FullyQualifiedName~Linear" -v minimal +``` + +Expected: + +- The two new sidewall long-descend execution tests pass at `ReplanCount == 0`. +- Existing linear execution guards remain green. + +### Task 6: Run Live Harness Verification + +**Files:** No production-file changes in this task. + +- [ ] **Step 1: Run the split live sidewall and linear matrices** + +Run: + +```bash +python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/descend +python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/flat +python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/ascend +python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter linear +``` + +Expected: + +- `sidewall-descend-gap5-dy-1-wo0` and `sidewall-descend-gap5-dy-1-wo1` move from mismatch to match. +- No accepted linear case regresses. +- Accepted runs show `replan_count=0` and no turn-stall trace. + +- [ ] **Step 2: Run the full matrix once after the split runs are green** + +Run: + +```bash +python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla +``` + +Expected: + +- The matrix summary reflects sidewall alignment without creating new linear mismatches. + +## Self-Review Checklist + +- The implementation intentionally does not modify `SprintJumpTemplate`, `SidewallParkourController`, or generic `MoveParkour` logic. +- Every new planner state transition is driven by an explicit same-level `Traverse`, so the resulting path stays visible and inspectable. +- The node-map key change is the only search-core behavior broad enough to affect unrelated routes; that is why the linear planner and execution guards are part of the mandatory validation set. diff --git a/docs/superpowers/plans/2026-04-19-unified-jump-move-plan.md b/docs/superpowers/plans/2026-04-19-unified-jump-move-plan.md new file mode 100644 index 0000000000..a5f27d6cb0 --- /dev/null +++ b/docs/superpowers/plans/2026-04-19-unified-jump-move-plan.md @@ -0,0 +1,154 @@ +# Unified Jump Move Refactor + +Date: 2026-04-19 +Branch: `pathing/jump-entry-direct-yaw` + +## Problem + +MCC's A* uses a hard-coded enumeration of ~220 `IMove` instances covering the +"jump family" (Traverse, Diagonal, Ascend, DiagonalAscend, DiagonalDescend, +Parkour, SidewallParkour). Each geometric variant is a separate IMove subclass +with its own `Calculate` method that re-implements the same physics checks +(head clearance, run-up, flight path, landing clearance, gap check). Symptoms: + +1. **Drift**: the same physics rule is implemented in 3-4 places. A fix to + `HasDominantAxisRunUp` does not automatically propagate to `HasRunUp`. +2. **Missing combinations silently become "impossible"**: until this week, the + planner had no `MoveParkour(dx=1, dz=2, yDelta=+1)` entry, so the diagonal + ascending jump (upper arrow in the user's pyramid image) was rejected + entirely even though the physics allow it. +3. **Slow expansion**: every A* node runs 220 feasibility checks and a lot of + them are obviously irrelevant for that position (e.g. sidewall checks when + there is no wall anywhere near the player). + +Baritone does not have this problem: `MovementParkour` is a single class that +dynamically probes reachable landings per direction, and its 8 `Moves` enums +cover the entire movement space. + +## Goal + +Bring MCC's jump family to a single parameterized move class with one unified +feasibility engine, then evolve to Baritone-style dynamic neighbor expansion. + +## Scope + +**In scope (unified under `MoveJump` + `JumpDescriptor`)**: + +- `MoveTraverse` (dy=0 cardinal) +- `MoveDiagonal` (dy=0 corner) +- `MoveAscend` (dy=+1 cardinal) +- `MoveDiagonalAscend` (dy=+1 corner) +- `MoveDiagonalDescend` (dy=-1 corner) +- `MoveParkour` (dy ∈ {+1, 0, -1, -2}, horiz up to 5 cardinal / sqrt(10) diag) +- `MoveSidewallParkour` (parkour + inner wall requirement) + +**Out of scope (stay as their own classes)**: + +- `MoveDescend` — dynamic variable-depth fall with water/ladder grab logic +- `MoveSprintDescend` — dynamic landing depth +- `MoveClimb` — ladder/vine vertical movement +- `MoveFall` — pure free fall + +These are "descent family" and have a different feasibility model (unknown +landing y, hazard scanning). Future refactor can unify them under a +`MoveFallToLanding` family but that is a separate effort. + +## Design + +### Data + +```csharp +public readonly record struct JumpDescriptor( + int XOffset, + int ZOffset, + int YDelta, + JumpFlavor Flavor); + +public enum JumpFlavor +{ + Walk, // dy=0, 1 block move, no jump (Traverse/Diagonal) + Step, // dy=±1, 1 block move with jump or step-off (Ascend/DiagDescend/DiagAscend) + SprintJump, // horiz >= 2 with or without dy (Parkour) + Sidewall, // SprintJump + inner-wall clearance (SidewallParkour) +} +``` + +The descriptor fully describes any jump-family move. `MoveType` (Traverse, +Diagonal, Ascend, Descend, Parkour) is derived from `(Flavor, dy, horiz)` so +downstream consumers (templates, cost tables) keep working. + +### Evaluator + +`JumpFeasibility.Evaluate(ctx, x, y, z, desc, ref result)` is the single source +of truth. It dispatches on `desc.Flavor` but shares the following primitives: + +1. **Guards**: `AllowParkour`, `AllowParkourAscend`, `MaxFallHeight`, `CanSprint`. +2. **Profile check**: geometry falls in the valid range for this flavor. +3. **Head clearance at start**: `y+2` always, plus `y+3` if ascending sprint jump. +4. **Standing material**: reject climbable (ladder/vine) takeoffs. +5. **Destination**: floor solid, body passable, head passable, no hazards. +6. **Run-up**: cold-start reach tables plus prepared-entry lookup + (`EntryPreparationState`). This replaces both `HasRunUp` and + `HasDominantAxisRunUp`. +7. **Flight path**: cardinal straight-line column sweep or diagonal + proportional-step sweep. Needs `y+3` clearance only when ascending. +8. **Wall** (Sidewall only): inner wall presence + outer clearance + arc span. +9. **Gap check**: reject when a direct walk would work. +10. **Cost**: unified sprint/walk cost × horizontal distance + penalties. + +### Step 1 — introduce evaluator, existing classes delegate + +No behavior change. Each of the 7 existing classes has `Calculate` shrunk to +a single `JumpFeasibility.Evaluate(...)` call with a descriptor derived from +its constructor args. All existing tests pass with the same pass/fail counts. + +### Step 2 — single `MoveJump` class + +Delete the 7 subclasses. `AStarPathFinder.BuildDefaultMoves` emits +`MoveJump(descriptor)` instances from a declarative list. Tests that instantiate +the old classes are updated to instantiate `MoveJump` with the equivalent +descriptor (or use factory helpers like `MoveJump.Parkour(dx, dz, dy)`). + +Templates (`SprintJumpTemplate`, `AscendTemplate`, `SidewallParkourController`, +etc.) dispatch on `Flavor` / `MoveType` already, so they need no changes. + +### Step 3 — dynamic neighbor expansion + +`AStarPathFinder.Calculate` currently loops over `_allMoves` for every popped +node. Replace with an `IMoveExpander[]` where each expander yields neighbors +on demand: + +```csharp +public interface IMoveExpander +{ + void Expand(CalculationContext ctx, int x, int y, int z, Action emit); +} +``` + +`JumpExpander.Expand` iterates the 4 cardinals + 4 diagonals. For each +direction, it asks "what is the furthest reachable landing?" by scanning from +max distance down to 1, emitting the first feasible result (the A* cost model +already disprefers short jumps when long jumps work). This yields ~8-16 +neighbors per node instead of 220. + +Baritone-style partial-path coefficients (`bestSoFar[6]`) are a separate +improvement; not bundled here. + +## Risk / rollback + +- Step 1 is behavior-preserving and easy to revert (delete evaluator, restore + the old `Calculate` bodies from git). +- Step 2 deletes code; revert means restoring from git. +- Step 3 changes the A* main loop. Keep the old `BuildDefaultMoves` path behind + an `_useDynamicExpansion` flag so we can A/B test in live `tools/test-parkour.py` + runs before deleting the old path. + +## Test strategy + +- `dotnet test MinecraftClient.Tests` after each step. Baseline is 21 failing + tests (all pre-existing on this branch). Target: exact same failure set at + each checkpoint. +- At Step 2 end, verify upper-arrow scenario (this task's motivating bug) still + plans correctly. +- At Step 3 end, run `tools/test-parkour.py` linear + sidewall + ceiling + scenarios on a real server. diff --git a/docs/superpowers/specs/2026-04-12-parkour-admissibility-design.md b/docs/superpowers/specs/2026-04-12-parkour-admissibility-design.md new file mode 100644 index 0000000000..d92dae294c --- /dev/null +++ b/docs/superpowers/specs/2026-04-12-parkour-admissibility-design.md @@ -0,0 +1,38 @@ +# Parkour Admissibility Hardening + +## Context +MoveParkour currently prepares sprint jumps with some previous Baritone-inspired checks, but certain configurations (e.g., missing run-up, blocked diagonal shoulders, landing into an immediate wall) still pass planning and fail at execution. The goal is to harden those admissions so that MoveParkour rejects unsafe shapes up front. + +## Requirements +- Embed conservative versions of Baritone’s reliability-first checks for run-up length, diagonal shoulder clearance, and landing overshoot into the pathing layer. +- Keep the new logic localized under a Parkour-specific helper so that future moves can share the same checks without duplicating code. +- Tighten MoveParkour to rely on the helper for admissibility decisions and to reject overshoots instead of tolerating them with a cost penalty. +- Add deterministic tests that illustrate the three requested behaviors (3×1 jump without run-up, 2×1 jump with clear takeoff/landing, diagonal jump blocked at a shoulder). +- Run only the targeted test command once with the new test class. + +## Design + +### ParkourFeasibility helper +- Provide `ParkourFeasibility.HasRunUp(ctx, x, y, z, xOffset, zOffset, yDelta)` that reuses the existing distance thresholds (2.5 with ascend, 3.5 otherwise) but also enforces that the block immediately behind the player is walkable (top surface plus passable columns at head and neck height). +- Provide `ParkourFeasibility.HasDiagonalShoulderClearance(ctx, x, y, z, xOffset, zOffset)` that rejects diagonal jumps unless both orthogonal neighbors at start are passable through the whole torso (y through y+2) so a blocked shoulder can’t clip the AABB. +- Provide `ParkourFeasibility.HasLandingOvershootClearance(ctx, destX, destY, destZ, xSign, zSign)` that fails when the two blocks immediately past the landing spot are not passable at body and head height, preventing collisions after landing. +- Keep the helper static under `Pathing/Moves` to allow reuse by other moves in the future; assume this is acceptable even though only MoveParkour currently uses it. + +### MoveParkour adjustments +- Before the existing flight-path, head-clearance, and landing/passability checks, call into the helper to verify run-up, diagonal shoulders, and overshoot. +- Remove the informational overshoot-penalty branch and instead treat blocked overshoot as an immediate rejection. +- Leave the current flight path, head clearance, and destination checks untouched to avoid regressions. + +### Testing +- Add `MinecraftClient.Tests.Pathing.Moves.MoveParkourTests` that reuse a flat stone world and toggle blocks to create the three scenarios: + 1. 3×1 side-wall jump lacking a run-up (expect `MoveResult.IsImpossible`). + 2. 2×1 jump with clear takeoff and landing (expect success and the expected destination). + 3. Diagonal jump whose start cardinal neighbor is blocked at shoulder height (expect rejection). +- Each test creates the context with `allowParkour: true`, instantiates the appropriate `MoveParkour`, runs `Calculate`, and asserts on `IsImpossible`. +- Tests will live next to other pathing tests but focus narrowly on parkour admissibility. + +## Validation +- Run `dotnet test MinecraftClient.Tests --filter MoveParkourTests`. + +## Open questions +- I assumed the helper should be reusable beyond MoveParkour; if you prefer it to stay internal, I can adjust the visibility surface. diff --git a/docs/superpowers/specs/2026-04-12-shared-server-isolated-mcc-sessions-design.md b/docs/superpowers/specs/2026-04-12-shared-server-isolated-mcc-sessions-design.md index 9fc0e37631..2312234df2 100644 --- a/docs/superpowers/specs/2026-04-12-shared-server-isolated-mcc-sessions-design.md +++ b/docs/superpowers/specs/2026-04-12-shared-server-isolated-mcc-sessions-design.md @@ -199,8 +199,8 @@ Expected defaults: Example commands: ```bash -mcc-debug -v 1.21.11 --session alice-a --username AliceA --file-input -mcc-debug -v 1.21.11 --session alice-b --username AliceB --file-input +mcc-debug -v 1.21.11-Vanilla --session alice-a --username AliceA --file-input +mcc-debug -v 1.21.11-Vanilla --session alice-b --username AliceB --file-input mcc-cmd --session alice-a "debug state" mcc-log-mcc --session alice-b mcc-kill --session alice-a @@ -266,7 +266,7 @@ When possible, the error should print the resolved repo root, shared server root ### Manual Verification Matrix 1. Build from two different worktrees at the same time and confirm isolated output roots. -2. Start one shared `1.21.11` server and confirm only one `mc-1_21_11` session exists. +2. Start one shared `1.21.11-Vanilla` server and confirm only one `mc-1_21_11-Vanilla` session exists. 3. Launch two MCC sessions from two different worktrees without explicit usernames and confirm distinct derived usernames. 4. Join both clients to the shared server and confirm neither client disconnects the other. 5. Send different commands through each session's input file and confirm only the intended client responds. diff --git a/docs/superpowers/specs/2026-04-12-slab-support-scheme-two-design.md b/docs/superpowers/specs/2026-04-12-slab-support-scheme-two-design.md new file mode 100644 index 0000000000..61be4082ae --- /dev/null +++ b/docs/superpowers/specs/2026-04-12-slab-support-scheme-two-design.md @@ -0,0 +1,299 @@ +# Slab Support Design, Scheme Two + +Date: 2026-04-12 +Status: Approved for implementation planning + +## Summary + +This design adds basic slab support to the current A* pathfinder without introducing half-block nodes. + +The goal is practical rather than perfect: make normal routing work across slabs, allow takeoff from slabs, allow landing on slabs when the fall is still within the current safe range, and keep the search space close to what it is today. + +The key constraint is that the current pathfinder stores integer `(x, y, z)` nodes and the execution layer still expects block-center waypoints. That is staying in place for this iteration. + +## What This Change Should Cover + +- Walking across bottom slabs, top slabs, and full blocks +- Moving up and down neighboring `0.5` block height differences +- Starting jumps from slabs +- Landing on slabs when the effective fall height is safe +- Keeping current parkour and descend behavior stable instead of trying to make slab parkour exhaustive + +## What This Change Will Not Cover + +- True half-block path nodes +- A general solution for all non-full-block surfaces such as stairs, carpets, snow layers, trapdoors, and similar terrain +- Full slab-aware parkour optimization +- A new cost model tuned around half-block travel times + +## Current Problem + +The physics layer can already step up `0.5` blocks and collide with slab shapes correctly. The planning layer cannot. It still treats movement as if every valid floor is a full block surface. + +That mismatch shows up in three places: + +- `MoveHelper` still answers most walkability questions at the `Material` level. +- The move set assumes floor height changes happen in whole blocks. +- Path segments still convert nodes to `(x + 0.5, y, z + 0.5)` and do not carry surface-height metadata. + +Because of that, basic slab terrain is either invisible to the planner or handled inconsistently. + +## Core Approach + +### Keep Integer Nodes + +The pathfinder will keep integer `(x, y, z)` nodes. This avoids doubling the vertical state space and keeps the current move graph shape. + +The cost is that slabs must be represented indirectly. That is acceptable for this iteration because the target is reliable routing, not full geometric precision. + +### Add Surface Profiles + +Planning will stop asking only "is this material solid?" and instead ask "what standing surface does this block column provide?" + +Each relevant block column will map to a small surface profile: + +- `None` +- `FullBlock` +- `TopSlab` +- `BottomSlab` + +For the first implementation, the source of truth is `BlockShapes`. Slabs already have distinct collision boxes there, including top and bottom variants. + +The profile also exposes the standing surface top Y relative to the block base: + +- `FullBlock` -> `1.0` +- `TopSlab` -> `1.0` +- `BottomSlab` -> `0.5` +- `None` -> not standable + +This gives the planner enough information to answer the questions it actually needs: + +- can the player stand here +- how high is the standing surface +- what is the effective fall height if the player lands here + +### Use an Alias-Y Model + +Nodes remain integer Y values even when the actual standing surface is at `.5`. + +The aliasing rule is: + +- a bottom slab standing surface inside block `(x, y - 1, z)` is still represented by node `y` +- the node Y means "feet are in this logical cell", not "feet are exactly on integer Y" + +This preserves compatibility with the current pathfinder and avoids widening the state space. + +## Movement Rules + +### Traverse And Diagonal Movement + +Flat movement will become "same effective standing height" movement, not just "same integer Y" movement. + +These cases should be allowed: + +- full block to full block +- full block to top slab +- top slab to full block +- bottom slab to bottom slab + +These cases should not be forced through the flat move set: + +- full block to bottom slab +- bottom slab to full block + +Those are `-0.5` and `+0.5` height changes and should be handled explicitly. + +### Half-Step Moves + +Add dedicated half-step moves: + +- `MoveHalfAscend` +- `MoveHalfDescend` + +First implementation scope: + +- cardinal half-step moves are included +- diagonal half-step moves are out of scope + +These moves are for adjacent columns whose standing surface differs by `0.5`. + +Execution for half-step moves must not press jump. The physics engine should handle them as a step-up or controlled walk-down. + +### Full-Block Ascend And Descend + +Existing `MoveAscend`, `MoveDescend`, `MoveFall`, and `MoveSprintDescend` remain in place, but their landing and clearance checks become surface-aware. + +The main difference is that the destination surface is no longer assumed to be exactly one block high relative to the block base. + +### Parkour + +Parkour is not getting a full slab rewrite in this iteration. + +The planner should: + +- allow takeoff from a slab if the start surface is valid +- allow landing on a slab if the effective fall and required clearance are valid +- avoid adding new slab-specific parkour move families in this change + +This keeps the change small enough to validate. + +## Safe Landing Rule For Bottom Slabs + +Bottom slabs should be allowed as fall destinations when the effective fall height is still within the current safe fall limit. + +This rule replaces the earlier blanket rejection. + +### Definition + +Use: + +`effectiveFallHeight = startSurfaceTopY - landingSurfaceTopY` + +with both heights measured in world coordinates. + +Given the current `MaxFallHeight = 3.0`, these examples should hold: + +- bottom slab to a bottom slab three blocks lower: allowed, because `0.5 -> -2.5` is an effective fall of `3.0` +- full block to a bottom slab `2.5` blocks lower: allowed +- full block to a bottom slab `3.5` blocks lower: rejected + +This matches the behavior we want: + +- support realistic slab landings +- keep the current safety ceiling +- avoid special casing by integer block count alone + +### Cost Model + +The fall cost table is still integer-based. For half-block fall distances, the first iteration will round up when consulting the fall-cost table. + +Examples: + +- `2.0` -> use `FallCost(2)` +- `2.5` -> use `FallCost(3)` +- `3.0` -> use `FallCost(3)` + +This is slightly conservative, which is fine for now. It avoids pretending the path is cheaper than the current planner knows how to represent. + +## Execution Layer Changes + +The execution layer needs a small amount of slab metadata so completion checks do not rely on loose tolerances alone. + +`PathSegment` should carry enough information for templates to know whether the start or end uses a half-height standing surface. A minimal version is: + +- start surface offset +- end surface offset + +with offsets of `0.0` or `-0.5` relative to the logical node Y. + +This metadata is only for execution and verification. It should not turn into a new search-state dimension. + +### New Templates + +Add: + +- `HalfAscendTemplate` +- `HalfDescendTemplate` + +Behavior: + +- face the target +- move forward +- do not sprint in the first implementation +- do not press jump +- use tighter completion checks that include the expected end surface offset + +Existing templates may also need small updates so slab takeoff and slab landing do not cause false stuck detection or early completion. + +## File-Level Impact + +Expected touch points: + +- `MinecraftClient/Pathing/Moves/MoveHelper.cs` +- `MinecraftClient/Pathing/Core/CalculationContext.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveTraverse.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveDiagonal.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveAscend.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveDescend.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveFall.cs` +- `MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs` +- new half-step move files under `MinecraftClient/Pathing/Moves/Impl/` +- `MinecraftClient/Pathing/Core/AStarPathFinder.cs` +- `MinecraftClient/Pathing/Execution/PathSegment.cs` +- new half-step template files under `MinecraftClient/Pathing/Execution/Templates/` +- template factory / executor wiring + +## Performance Expectations + +This design should not materially expand the search space because nodes stay integer-based. + +The expected overhead comes from: + +- extra `BlockShapes` lookups during move validation +- a few more comparisons per move +- a small number of extra move types + +That is a constant-factor increase, not a state explosion. + +The main thing to avoid is introducing separate `.0` and `.5` Y states into the open set. This design does not do that. + +## Risks + +### Alias-Y Drift + +The biggest risk is mismatch between logical node Y and the player's actual surface height. If the segment metadata is too thin, templates may oscillate, finish too early, or trigger unnecessary replans. + +### Clearance Mistakes + +A bottom slab under a low ceiling is the easiest place to get this wrong. Surface-aware standability is not enough by itself. The move checks still need to verify body and head clearance against the actual shapes involved. + +### Scope Creep + +Once slab support works, stairs and snow layers will look tempting. They are out of scope for this change. + +## Test Plan + +### Planner-Level Cases + +Build focused tests around these scenarios: + +- full -> bottom slab +- bottom slab -> full +- bottom slab -> bottom slab +- full -> top slab +- top slab -> full +- slab takeoff for jump and parkour moves +- solid landing on top slab +- solid landing on bottom slab with effective fall `<= 3.0` +- solid landing on bottom slab with effective fall `> 3.0` +- slab under a low ceiling + +### Physics And Execution Checks + +Use `tools/sim_jump_reach.py` to validate the intended reachability envelope and then run local server checks for: + +- `/goto` across mixed full-block and slab terrain +- repeated slab transitions without replan loops +- takeoff from slab to slab and slab to full block +- landing on bottom slabs at `2.5` and `3.0` effective fall distances +- rejection of `3.5` effective-fall bottom-slab landings + +## Implementation Notes + +The first implementation should favor readable helper code over micro-optimizing shape checks. If the new helper becomes hot, caching can be added after behavior is stable. + +The safest rollout order is: + +1. Add surface-profile helpers +2. Update landing logic and safe-fall logic +3. Add half-step moves and templates +4. Expand move coverage only after the basic route cases are stable + +## Decision + +Proceed with scheme two: + +- integer nodes stay +- slab surfaces are modeled through shape-aware helpers +- bottom slab landings are allowed when effective fall height stays within the existing safe limit +- no attempt is made to solve the general non-full-block terrain problem in this pass diff --git a/docs/superpowers/specs/2026-04-13-theory-aligned-pathing-regression-design.md b/docs/superpowers/specs/2026-04-13-theory-aligned-pathing-regression-design.md new file mode 100644 index 0000000000..b10f943f6a --- /dev/null +++ b/docs/superpowers/specs/2026-04-13-theory-aligned-pathing-regression-design.md @@ -0,0 +1,207 @@ +# Theory-Aligned Pathing Regression + +## Context +MCC already has two useful but separate assets for pathing and parkour validation: + +- [tools/sim_jump_reach.py](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/sim_jump_reach.py) models a subset of vanilla jump reachability and can answer whether specific jump shapes are theoretically reachable. +- The live harness scripts under [tools/](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools) validate real MCC behavior on a local server, but they currently act as curated scenario suites rather than a stable projection of one theoretical source of truth. + +The immediate goal is to make the simulator the authority for first-wave jump capability claims, then align a smaller live regression layer to that authority. This first wave must stay intentionally narrow: it should cover only movement families already modeled by `sim_jump_reach.py`, not every higher-level execution behavior MCC currently exercises live. + +## Requirements +- Treat `tools/sim_jump_reach.py` as the authority for first-wave jump capability expectations. +- Restrict first-wave coverage to movement families already modeled by the simulator: + - linear flat jumps + - linear ascend jumps + - linear descend jumps + - neo jumps + - ceiling-constrained or headhitter jumps +- Produce both machine-readable and human-readable theory outputs from the same source data. +- Define live regression coverage through canonical buckets, not by replaying every theoretical case. +- Ensure every theory-aligned live case can be traced back to one or more theory case IDs. +- Keep existing specialized live suites available, but do not treat them as part of the first-wave theory authority. +- Preserve the current MCC local workflow based on `tools/mcc-env.sh`, `mcc-debug`, tmux-backed local sessions, and shared local servers. + +## Design + +### Recommended approach +Three approaches were considered: + +1. Hand-maintain theory expectations and live cases separately. +2. Make the simulator authoritative, then select canonical live buckets from its output. +3. Fully auto-generate all live cases from simulator output. + +Approach 2 is the recommended first-wave design. It keeps one theory authority, creates a stable contract for live coverage, and avoids over-scoping the first iteration with full live generation. + +### Capability layers +The regression system should be split into three layers with explicit responsibilities: + +- Theory matrix + - Generated from `tools/sim_jump_reach.py`. + - Defines what MCC is expected to support for the first-wave movement families. +- Canonical live coverage + - Derived from the theory matrix by bucket rules. + - Validates representative easy, boundary, and reject scenarios on a real server. +- Specialized live suites + - Existing higher-level pathing suites such as mixed-route, braking, or landing-recovery scenarios. + - Remain valuable, but are explicitly outside the first-wave theory contract until their behaviors also have a stable theoretical source. + +This separation prevents higher-level execution scenarios from contaminating the meaning of the first-wave authority layer. + +### Theory matrix schema +The theory matrix should be stored as a fine-grained case table. Each row represents one distinct theoretical movement judgment. The table should include at least: + +- `case_id` +- `family` +- `subfamily` +- `movement_mode` +- `momentum_ticks` +- `gap_blocks` +- `delta_y` +- `ceiling_height` +- `wall_width` +- `expected_reachable` +- `landing_x` +- `apex_y` +- `margin` +- `notes` + +Recommended family and subfamily values for the first wave: + +- `linear` + - `flat` + - `ascend` + - `descend` +- `neo` +- `ceiling` + - `headhitter` + +The important contract is that `expected_reachable` comes from the simulator, not from handwritten shell-script expectations. + +### Canonical bucket model +Live coverage should not replay every theoretical case. Instead, the theory matrix should be grouped into canonical buckets that classify the live representative scenarios. Each canonical bucket should have stable dimensions: + +- `family` +- `subfamily` +- `movement_mode` +- `difficulty_band` + +The first-wave difficulty bands are: + +- `easy` + - clearly reachable with generous margin +- `boundary` + - close to the theoretical edge and most likely to regress +- `reject` + - theoretically unreachable and expected to be rejected live + +Each canonical live case must reference: + +- `case_id` +- `bucket_id` +- `expected_result` +- `world_recipe_id` +- `start` +- `goal` + +This ensures the live harness is executing a curated projection of the theory matrix rather than inventing expectations independently. + +### First-wave movement scope +The first-wave theory authority covers only what `sim_jump_reach.py` already models directly: + +- linear flat jumps +- linear ascend jumps +- linear descend jumps +- neo jumps +- ceiling-constrained or headhitter jumps + +The first wave explicitly does not promote these existing live-only behaviors into theory authority: + +- repeated parkour chains +- parkour landing recovery into turns +- braking and speed-carry transitions +- mixed long-route execution +- segment-to-segment transition behavior + +Those scenarios remain useful, but they belong to specialized live suites until a simulator-backed authority exists for them. + +### Output artifacts +The simulator-backed generation step should produce three synchronized outputs from the same in-memory data: + +- JSON + - primary machine-readable artifact for automation +- CSV + - convenient for inspection, filtering, and quick diffs +- Markdown + - human-readable capability summary and bucket overview + +The design requires these outputs to be generated in one pass so they cannot silently drift apart. + +### Live suite reorganization +The first-wave live layer should be organized into theory-aligned and specialized suites. + +Theory-aligned suites: + +- Refactor [tools/test-parkour.sh](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/test-parkour.sh) into the main theory-aligned linear-jump suite. +- Add a dedicated live suite for neo and ceiling-constrained cases. + +Specialized live suites retained outside the theory contract: + +- [tools/test-pathing-jump-combos.sh](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/test-pathing-jump-combos.sh) +- [tools/test-pathing-template-regressions.sh](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/test-pathing-template-regressions.sh) +- [tools/test-pathing-long-routes.sh](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/test-pathing-long-routes.sh) +- [tools/test-transition-braking.sh](/home/ryan/Minecraft/Minecraft-Console-Client-milutinke/tools/test-transition-braking.sh) + +This lets MCC keep broader pathing smoke coverage without pretending every advanced live script is already grounded in the simulator. + +### Execution and comparison flow +The first-wave regression pipeline should be one directional: + +1. Generate the full theory matrix from `sim_jump_reach.py`. +2. Derive canonical buckets and canonical live cases from that matrix. +3. Run the theory-aligned live suites against the canonical live case set. +4. Join live results back to theory case IDs and produce a comparison report. + +Live suites must not encode the truth model themselves. They are executors and verifiers only. + +### Result model +The comparison layer should use these result classes: + +- `expected_pass / live_pass` +- `expected_pass / live_fail` +- `expected_reject / live_reject` +- `expected_reject / live_unexpected_pass` +- `invalid_live_case` + +`invalid_live_case` is reserved for harness or environment faults such as malformed geometry, invalid goals, startup failure, or RCON and session issues. It should not be treated as a capability result. + +### File layout +The first-wave implementation should keep the layout conservative: + +- Keep `tools/sim_jump_reach.py` as the theory entry point. +- Add theory export outputs under `tools/` or a closely related generated-output location. +- Add a canonical live-case manifest under `tools/` or a nearby data location suitable for shell-script consumption. +- Reuse existing `tools/mcc-env.sh` helpers, `mcc-debug`, tmux-backed MCC sessions, and shared local server management. + +No change is required to the core MCC runtime architecture for the first-wave design itself. + +### Delivery order +The implementation should proceed in this order: + +1. Stabilize theory export generation from `sim_jump_reach.py`. +2. Define canonical bucket and world-recipe selection rules. +3. Convert `tools/test-parkour.sh` to consume canonical theory-aligned cases. +4. Add the theory-aligned neo and ceiling live suite. +5. Leave specialized live suites in place with documentation clarifying that they are outside the first-wave theory authority. + +This order keeps truth-generation ahead of live execution and avoids locking shell suites to premature handwritten expectations. + +## Validation +- Generate the theory matrix and confirm JSON, CSV, and Markdown outputs are produced from the same dataset. +- For each first-wave bucket, require at least one canonical `easy`, `boundary`, and `reject` live case where applicable to that movement family. +- Record theory case ID, bucket ID, world recipe ID, expected result, live result, and MCC log path for every theory-aligned live case. +- Run theory-aligned live suites using the existing local server workflow through `source tools/mcc-env.sh` and `mcc-debug`. +- Keep specialized live suites runnable as separate checks, but do not block first-wave theory alignment on converting them. + +## Open questions +- None for the first-wave scope. Higher-level mixed execution behaviors are intentionally deferred until a simulator-backed authority exists for them. diff --git a/docs/superpowers/specs/2026-04-18-parkour-worker-pool-design.md b/docs/superpowers/specs/2026-04-18-parkour-worker-pool-design.md new file mode 100644 index 0000000000..3cac1bb2ba --- /dev/null +++ b/docs/superpowers/specs/2026-04-18-parkour-worker-pool-design.md @@ -0,0 +1,194 @@ +# Parkour Worker Pool Design + +**Date:** 2026-04-18 + +**Goal** + +Make `tools/test-parkour.py` behave like a real parallel harness on `1.21.11-Vanilla`: keep `--parallel N` as `N` long-lived MCC workers, classify harness failures separately from product failures, and emit artifacts that make unattended runs easy to inspect. + +## Problem Statement + +The current script has two real operational problems. + +1. Server and RCON readiness are assumed too early. In prior use this caused an immediate `ConnectionRefusedError` when the server had been stopped by a previous harness. +2. The parallel path launches and tears down a fresh MCC session for every case. That produces large startup overhead, leaves many session directories behind, and makes logs hard to correlate with results. + +The current script also has observability gaps. + +- Skipped cases are only visible in stdout. +- JSONL rows do not carry enough metadata to jump directly to the relevant logs. +- There is no per-run summary artifact beyond terminal output. + +## Non-Goals + +- Changing parkour theory generation or expected pass/reject boundaries. +- Reworking the live outcome rules that treat any replan or turn-stall as a failure for pass cases. +- Generalizing the harness to multi-version shared-state parallelism. + +## Chosen Approach + +Use a long-lived worker pool. + +- Start `N` MCC workers once. +- Assign whole `group_key()` batches to workers to preserve stop-at-first-failure semantics inside a group. +- Reuse the same worker for multiple cases by resetting player state between cases. +- Restart only the individual worker that becomes unhealthy. + +This keeps isolation strong enough for unattended live testing while removing the main startup bottleneck. + +## Architecture + +### Run Controller + +The main process will build the course matrix and create a dedicated run directory under `/tmp/parkour-runs/-/`. + +It will own: + +- server/RCON readiness checks +- world build phase +- worker pool startup +- group scheduling +- summary generation + +### Worker Lifecycle + +Each worker will keep a stable: + +- `worker_id` +- `session` +- `username` +- MCC log path +- worker event log + +Worker lifecycle: + +1. launch MCC once +2. wait for join confirmation +3. enable debug mode +4. run many assigned cases with reset in between +5. if unhealthy, recycle just that worker +6. quit cleanly during harness shutdown + +### Case Execution + +Each case will still: + +- teleport to the start +- verify local sync via `debug state` +- run `goto` +- sample position/yaw during execution +- parse `[PathMetric]` telemetry +- classify into `pass`, `reject`, `fail`, or a harness-specific error + +The zero-replan and zero-turn-stall rule remains unchanged. + +### Artifacts + +Each run directory will contain: + +- `manifest.json` +- `results.jsonl` +- `summary.json` +- `summary.md` +- `events.jsonl` +- `workers//worker.log` +- `cases/.json` + +Every result row will include: + +- `case_id` +- `family` +- `subfamily` +- `expected` +- `outcome` +- `matched` +- `worker` +- `session` +- `log_path` +- `event_log_path` +- `replan_count` +- `turn_stall_count` +- `near_goal` +- `total_ticks` +- `final_position` +- `duration_ms` +- `skip_reason` +- `error_kind` + +Skipped cases will be recorded, not just printed. + +## Failure Taxonomy + +Observed product behavior and harness behavior must be separated. + +Product-side outcomes: + +- `pass` +- `reject` +- `fail` + +Harness-side outcomes: + +- `harness_rcon_unavailable` +- `harness_worker_launch_failed` +- `harness_join_timeout` +- `harness_start_sync_failed` +- `harness_worker_lost` + +`matched` remains the top-level boolean used by summaries. + +## Logging Model + +Terminal output becomes high-signal progress output: + +- worker launch / restart +- case start / case finish +- group skip decisions +- mismatch lines +- final summaries by family and outcome + +Detailed evidence moves into run artifacts. + +## Testing Strategy + +### Python unit coverage + +Extend `tools/tests/test_test_parkour_metrics.py` to cover: + +- worker session naming +- run directory naming +- result row schema +- skip row emission +- summary aggregation +- harness error classification + +Extend `tools/tests/test_pathing_live_scripts.py` to cover: + +- new CLI-compatible output expectations +- `--list-cases` stability + +### Real-server validation + +After refactor: + +- run targeted Python tests +- run the full `tools/tests` suite +- run a real `tools/test-parkour.py --filter linear --parallel 6 --version 1.21.11-Vanilla` +- confirm that workers are reused across multiple cases and that summary artifacts are emitted + +## Risks And Mitigations + +- Worker state leaks between cases. + - Mitigation: centralize `reset_worker_state()` and recycle unhealthy workers. +- Mixed threaded stdout becomes unreadable. + - Mitigation: keep stdout terse and persist details to per-worker logs. +- Shared server state still limits aggressive parallelism. + - Mitigation: keep one shared version/server per run and continue atomic group scheduling. + +## Acceptance Criteria + +- A stopped or not-yet-ready server is reported as a harness problem instead of a raw socket traceback. +- `--parallel 6` keeps approximately six long-lived MCC workers instead of one worker per case. +- Results include executed cases and skipped cases. +- A completed unattended run can be inspected from `summary.md` and `summary.json` without replaying terminal output. +- The zero-replan and zero-turn-stall requirement remains enforced for pass cases. diff --git a/docs/superpowers/specs/2026-04-19-sidewall-runup-precondition-design.md b/docs/superpowers/specs/2026-04-19-sidewall-runup-precondition-design.md new file mode 100644 index 0000000000..5728e1ff0b --- /dev/null +++ b/docs/superpowers/specs/2026-04-19-sidewall-runup-precondition-design.md @@ -0,0 +1,175 @@ +# Sidewall Runup Precondition Planning + +## Context + +`sidewall` parkour still has a static-entry gap in the planner: some first-jump sidewall profiles are only physically reliable when the player backs up first, rebuilds sprint momentum along the dominant axis, returns to the launch origin, and then jumps. The current pathfinder cannot represent that distinction because it keys search nodes by position only and only exposes `PreviousMoveType` to move feasibility. + +The user explicitly rejected solving this with a runtime-only template hack. The required behavior is planner-driven: if a sidewall jump has an entry-momentum precondition, the produced path should contain explicit ground segments for the setup action, and accepted executions must still complete at `replan_count=0` and `turn_stall_count=0`. + +## Requirements + +- Keep the current `linear` matrix green. Do not loosen generic linear parkour rules to make sidewall pass. +- Make the setup action planner-visible. The final path must contain explicit ground segments before the first jump instead of hiding the behavior inside `SprintJumpTemplate` or `SidewallParkourController`. +- Keep scope narrow in phase 1. Only static-entry first-jump sidewall profiles may use the new setup mechanism. +- Do not hardcode case ids. Activation must be geometry/profile driven. +- Preserve the current carry-in path semantics. If a sidewall jump is already entered with valid carry, the planner must not inject a setup loop. +- In the current no-entity-collision environment, accepted sidewall cases must still run with `replan_count=0` and `turn_stall_count=0`. + +## Non-Goals + +- No generic “find any staging area behind me” feature in phase 1. +- No new runtime-only sidewall template family. +- No expansion to `neo`, `ceiling`, or generic `Parkour` in this change. +- No attempt to fix the unrelated `.NET` baseline failures outside the targeted sidewall/linear guard surface. + +## Design + +### Scope and activation + +Phase 1 adds a narrow planner-side precondition for static-entry sidewall jumps that need extra runway. Based on the latest verified matrix, the first activation predicate should be: + +- `ParkourProfile.Sidewall` +- no carry-in (`PreviousMoveType` is not `Parkour` or `Descend`) +- descending sidewall (`yDelta == -1`) +- dominant horizontal distance `== 5` + +This is intentionally narrow because the current verified live mismatch set is concentrated there and `linear` is already fully green. The predicate is profile-based, not case-id based, so it still follows geometry rather than scenario names. + +### Search-state extension + +The planner needs an extra discrete state to distinguish: + +- standing at the launch origin with no setup +- moving backward to build setup runway +- moving forward back toward the launch origin +- standing at the launch origin after completing the required setup + +Add two new core types under `MinecraftClient/Pathing/Core/`: + +- `EntryPreparationKind` +- `EntryPreparationState` + +`EntryPreparationState` should carry: + +- preparation kind, phase, and whether the state is empty +- launch origin (`OriginX`, `OriginY`, `OriginZ`) +- dominant forward axis (`ForwardX`, `ForwardZ`) +- required setup length in blocks +- completed backward steps +- completed return steps + +`PathNode` gets an `EntryPreparation` field. `CalculationContext` gets `CurrentEntryPreparation` so move feasibility can inspect the current node’s preparation state during expansion. `AStarPathFinder` must stop keying nodes by packed position only and instead key by `position + entry preparation state`. + +This is the critical design point: explicit path segments alone are not enough. Without a search-state distinction, A* would return to the same origin block and still evaluate the jump as a plain zero-entry sidewall jump. + +### How setup appears in the path + +The setup action should not introduce a new runtime `MoveType`. The visible path should be composed of ordinary ground moves: + +1. one or more `Traverse` segments backward along the dominant axis +2. the same number of `Traverse` segments forward along the dominant axis +3. the `Parkour` segment from the original launch block + +That keeps the execution layer simple. `PathSegmentBuilder` already marks a ground segment whose next segment is `Parkour` as `PrepareJump`, so the last forward traverse segment will naturally receive jump-ready transition hints without adding a new template concept. + +### Starting, advancing, and clearing setup state + +The setup state is seeded and advanced in the pathfinder, not inside the movement templates. + +#### Starting setup + +When A* is expanding a node with empty `EntryPreparationState`, and it considers a one-block `Traverse` that moves exactly opposite the dominant axis of a sidewall jump that requires setup from the current origin, the neighbor should receive a seeded `EntryPreparationState`: + +- origin set to the current block +- forward axis set to the jump’s dominant direction +- required steps set from the profile helper +- backward steps initialized to `1` +- return steps initialized to `0` + +This keeps the setup path explicit because the first action is still an actual ground move. + +#### Advancing setup + +While the node is in a setup state: + +- a same-level `Traverse` exactly opposite the forward axis increments backward progress until the required count is reached +- after backward progress is full, a same-level `Traverse` exactly along the forward axis increments return progress +- when return progress reaches the required count, the destination must be the original launch origin and the state becomes “prepared” + +#### Clearing setup + +Any other move clears the setup state immediately: + +- `Diagonal`, `Ascend`, `Descend`, `Fall`, `Climb`, `Parkour` +- same-level `Traverse` in the wrong direction +- any move that changes Y +- any return that overshoots the launch origin + +This keeps the mechanic narrow and predictable. The phase-1 behavior is “straight backward, then straight forward, then jump,” not a general-purpose staged maneuver planner. + +### Sidewall admissibility split + +`ParkourFeasibility` should stop treating this as a simple yes/no runway check. It needs to distinguish: + +- physically impossible profile +- directly admissible profile +- profile admissible only after explicit setup + +Add a helper such as `TryGetRequiredStaticEntryRunupSteps(...)` that returns `0` for direct-entry sidewall jumps and a positive step count for setup-required profiles. In phase 1, this returns `2` for the narrow long-descend sidewall predicate above and `0` otherwise. + +`MoveSidewallParkour` then uses the split as follows: + +- if the profile does not require setup, keep the current dominant-axis admissibility logic +- if the profile requires setup, reject unless `CurrentEntryPreparation` is a prepared sidewall setup for the same launch origin, same forward axis, and same required length +- if carry-in is present, bypass setup entirely and keep the current carry behavior + +### Execution impact + +Execution changes should be avoided in phase 1. The produced path now contains explicit ground runway segments before the first sidewall jump, which means the existing transition logic should already hand the last forward traverse segment a `PrepareJump` exit transition. + +Do not modify `SprintJumpTemplate` or `SidewallParkourController` unless targeted tests prove the new path shape causes a fresh runtime regression. The primary fix is planner-side. + +## Testing Strategy + +### Targeted .NET regressions + +Add or update tests in: + +- `MinecraftClient.Tests/Pathing/Moves/MoveSidewallParkourTests.cs` +- `MinecraftClient.Tests/Pathing/Execution/LivePathingRegressionTests.cs` +- `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs` + +The key assertions are: + +- static-entry long-descend sidewall rejects without prepared setup +- A* for that profile succeeds and prepends explicit `Traverse` setup segments before the first `Parkour` +- the last setup traverse ends back on the original launch block +- the same sidewall chain still executes with `ReplanCount == 0` +- the linear planner path shape remains unchanged, with no inserted setup segments + +### Live harness + +Use `tools/test-parkour.py` in split runs to avoid stop-at-first-failure masking: + +- `--filter sidewall/descend` +- `--filter sidewall/flat` +- `--filter sidewall/ascend` +- `--filter linear` + +Then run the full matrix once after the targeted runs pass. + +## Risks and mitigations + +- Search-space growth: mitigated by activating setup only for one narrow sidewall profile and by clearing the preparation state on any non-axis-aligned move. +- Linear regression: mitigated by not touching `MoveParkour` or generic linear admissibility in phase 1. +- Runtime drift despite planner fix: mitigated by keeping the new path shape limited to ordinary `Traverse` plus existing `Parkour`, then proving `0 replan` and `0 turn stall` with targeted tests and live harness evidence. + +## Validation + +- `dotnet test MinecraftClient.Tests --filter "FullyQualifiedName~MoveSidewallParkourTests|FullyQualifiedName~LivePathingRegressionTests|FullyQualifiedName~PathSegmentManagerTests"` +- `dotnet test MinecraftClient.Tests --filter "FullyQualifiedName~Linear"` +- `python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/descend` +- `python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/flat` +- `python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter sidewall/ascend` +- `python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla --filter linear` +- `python3 tools/test-parkour.py --parallel 6 --version 1.21.11-Vanilla` diff --git a/tools/README.md b/tools/README.md index be83c64dd4..c7a136489e 100644 --- a/tools/README.md +++ b/tools/README.md @@ -10,8 +10,8 @@ The `tools/` directory also contains the shell helpers used for day-to-day MCC d ```bash source tools/mcc-env.sh -mc-start 1.21.11 -mcc-debug -v 1.21.11 --file-input +mc-start 1.21.11-Vanilla +mcc-debug -v 1.21.11-Vanilla --file-input mcc-cmd "debug state" mcc-publish --rid linux-x64 ``` @@ -51,13 +51,15 @@ Two types of data can be used as input: ### Decompiling a new MC version ```bash -# Server side (default) — also downloads server.jar into MinecraftOfficial/downloads// -tools/decompile.sh --version 1.21.9 +# Server side (default) — downloads server.jar into $MCC_SERVERS/-Vanilla/ +tools/decompile.sh --version 1.21.9-Vanilla # Client side tools/decompile.sh --version 1.21.9 --side CLIENT ``` +For server-side runs, the decompiled source still lands in `MinecraftOfficial/-decompiled/`, while the runnable local server directory becomes `$MCC_SERVERS/-Vanilla/`. + If you keep server assets outside the repo, set `MCC_SERVERS=/path/to/servers` before using `tools/mcc-env.sh` or `tools/start-server.sh`. The script auto-downloads `MinecraftDecompiler.jar` from GitHub releases if it doesn't exist. diff --git a/tools/decompile.sh b/tools/decompile.sh index 791431ba19..9f922b8c1f 100644 --- a/tools/decompile.sh +++ b/tools/decompile.sh @@ -6,13 +6,14 @@ # ./tools/decompile.sh --version [--side SERVER|CLIENT] # # Examples: -# ./tools/decompile.sh --version 1.21.11 +# ./tools/decompile.sh --version 1.21.11-Vanilla # ./tools/decompile.sh --version 1.21.11 --side CLIENT set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" MC_OFFICIAL="$REPO_ROOT/MinecraftOfficial" +SERVERS_ROOT="${MCC_SERVERS:-$MC_OFFICIAL/downloads}" DECOMPILER_JAR="$MC_OFFICIAL/MinecraftDecompiler.jar" DECOMPILER_REPO="MaxPixelStudios/MinecraftDecompiler" @@ -27,7 +28,7 @@ while [[ $# -gt 0 ]]; do echo "Usage: $0 --version [--side SERVER|CLIENT]" echo "" echo "Options:" - echo " --version Minecraft version (e.g. 1.21.11)" + echo " --version Minecraft version or local server dir (e.g. 1.21.11 or 1.21.11-Vanilla)" echo " --side SERVER (default) or CLIENT" exit 0 ;; @@ -46,6 +47,16 @@ if [[ "$SIDE" != "SERVER" && "$SIDE" != "CLIENT" ]]; then exit 1 fi +MC_VERSION="${VERSION%-Vanilla}" +if [[ -z "$MC_VERSION" ]]; then + MC_VERSION="$VERSION" +fi + +SERVER_DIR_NAME="$VERSION" +if [[ "$SIDE" == "SERVER" && "$VERSION" != *-Vanilla ]]; then + SERVER_DIR_NAME="${MC_VERSION}-Vanilla" +fi + # --- Ensure MinecraftDecompiler.jar exists --- if [[ ! -f "$DECOMPILER_JAR" ]]; then echo "MinecraftDecompiler.jar not found, downloading latest release..." @@ -71,11 +82,11 @@ fi SIDE_LOWER="$(echo "$SIDE" | tr '[:upper:]' '[:lower:]')" if [[ "$SIDE" == "SERVER" ]]; then - REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-remapped.jar" - DECOMPILED_DIR="$MC_OFFICIAL/${VERSION}-decompiled" + REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${MC_VERSION}-remapped.jar" + DECOMPILED_DIR="$MC_OFFICIAL/${MC_VERSION}-decompiled" else - REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-${SIDE_LOWER}-remapped.jar" - DECOMPILED_DIR="$MC_OFFICIAL/${VERSION}-${SIDE_LOWER}-decompiled" + REMAPPED_JAR="$MC_OFFICIAL/remapped_jar/${MC_VERSION}-${SIDE_LOWER}-remapped.jar" + DECOMPILED_DIR="$MC_OFFICIAL/${MC_VERSION}-${SIDE_LOWER}-decompiled" fi if [[ -d "$DECOMPILED_DIR" ]]; then @@ -92,12 +103,12 @@ VERSION_URL=$(curl -sL "$MANIFEST_URL" | python3 -c " import json, sys data = json.load(sys.stdin) for v in data['versions']: - if v['id'] == '$VERSION': + if v['id'] == '$MC_VERSION': print(v['url']) break ") if [[ -z "$VERSION_URL" ]]; then - echo "Error: version $VERSION not found in Mojang launcher manifest." + echo "Error: version $MC_VERSION not found in Mojang launcher manifest." exit 1 fi @@ -109,9 +120,12 @@ data = json.load(sys.stdin) print('true' if '$MAPPING_KEY' in data.get('downloads', {}) else 'false') ") -echo "=== Decompiling Minecraft $VERSION ($SIDE) ===" +echo "=== Decompiling Minecraft $MC_VERSION ($SIDE) ===" echo " Remapped JAR: $REMAPPED_JAR" echo " Decompiled: $DECOMPILED_DIR" +if [[ "$SIDE" == "SERVER" ]]; then + echo " Server dir: $SERVERS_ROOT/$SERVER_DIR_NAME" +fi echo " Obfuscated: $HAS_MAPPINGS" echo "" @@ -120,7 +134,7 @@ cd "$MC_OFFICIAL" if [[ "$HAS_MAPPINGS" == "true" ]]; then # Obfuscated version: use --version/--side to auto-download jar + mappings + deobfuscate java -jar "$DECOMPILER_JAR" \ - --version "$VERSION" \ + --version "$MC_VERSION" \ --side "$SIDE" \ --decompile \ --output "$REMAPPED_JAR" \ @@ -129,14 +143,14 @@ else # Unobfuscated version (26.1+): download jar, extract inner jar from bundle, decompile directly. # MinecraftDecompiler requires --mapping-path with --input, but unobfuscated versions # have no mappings. We use Vineflower directly instead. - echo "No Proguard mappings for $VERSION; decompiling without deobfuscation." + echo "No Proguard mappings for $MC_VERSION; decompiling without deobfuscation." JAR_URL=$(echo "$VERSION_META" | python3 -c " import json, sys data = json.load(sys.stdin) print(data['downloads']['${SIDE_LOWER}']['url']) ") - ORIGINAL_JAR="$MC_OFFICIAL/remapped_jar/${VERSION}-${SIDE_LOWER}-original.jar" + ORIGINAL_JAR="$MC_OFFICIAL/remapped_jar/${MC_VERSION}-${SIDE_LOWER}-original.jar" if [[ ! -f "$ORIGINAL_JAR" ]]; then echo "Downloading ${SIDE_LOWER}.jar ..." curl -L -o "$ORIGINAL_JAR" "$JAR_URL" @@ -175,13 +189,13 @@ echo "" echo "=== Done ===" echo "Decompiled source: $DECOMPILED_DIR" -# --- For SERVER side, also ensure downloads//server.jar exists --- +# --- For SERVER side, also ensure downloads//server.jar exists --- if [[ "$SIDE" == "SERVER" ]]; then - DOWNLOADS_DIR="$MC_OFFICIAL/downloads/$VERSION" + DOWNLOADS_DIR="$SERVERS_ROOT/$SERVER_DIR_NAME" if [[ ! -f "$DOWNLOADS_DIR/server.jar" ]]; then mkdir -p "$DOWNLOADS_DIR" echo "" - echo "Downloading server.jar for $VERSION into $DOWNLOADS_DIR ..." + echo "Downloading server.jar for $MC_VERSION into $DOWNLOADS_DIR ..." SERVER_JAR_URL=$(echo "$VERSION_META" | python3 -c " import json, sys data = json.load(sys.stdin) diff --git a/tools/mcc-debug.sh b/tools/mcc-debug.sh index d0ca0e782f..620f5bdd72 100644 --- a/tools/mcc-debug.sh +++ b/tools/mcc-debug.sh @@ -42,7 +42,6 @@ DO_BUILD=true DEBUG_ON=false FILE_INPUT=false BUILD_ROOT="$(_mcc_build_root)" -BUILD_ROOT_ENV_PREFIX="" while [[ $# -gt 0 ]]; do case "$1" in @@ -102,8 +101,6 @@ fi if [[ "${MCC_BUILD_MODE:-local}" == "tmpfs" ]]; then mkdir -p "$BUILD_ROOT" - printf -v BUILD_ROOT_QUOTED '%q' "$BUILD_ROOT" - BUILD_ROOT_ENV_PREFIX="MCC_BUILD_ROOT=$BUILD_ROOT_QUOTED " fi SESSION_ROOT="$(_mcc_session_root "$SESSION")" @@ -224,11 +221,25 @@ rm -f "$PID_FILE" MCC_ARGS=("$CFG" "$USERNAME" "-" "localhost:$PORT") MCC_ARGS_CMD="$(printf '%q ' "${MCC_ARGS[@]}")" +RUNTIME_APP="$(_mcc_runtime_app_path || true)" +if [[ -z "$RUNTIME_APP" ]]; then + echo " Failed to find built MCC runtime under $(_mcc_runtime_output_dir)" >&2 + echo " Build first with: source tools/mcc-env.sh && mcc-build" >&2 + exit 1 +fi + +if [[ "$RUNTIME_APP" == *.dll ]]; then + MCC_LAUNCHER=(dotnet "$RUNTIME_APP") +else + MCC_LAUNCHER=("$RUNTIME_APP") +fi +MCC_LAUNCHER_CMD="$(printf '%q ' "${MCC_LAUNCHER[@]}")" + if [[ "$MODE" == "tui" ]]; then # TUI mode: needs a real tty - no pipes or redirects allowed tmux kill-session -t "$MCC_TMUX_SESSION" 2>/dev/null || true tmux new-session -d -s "$MCC_TMUX_SESSION" -x 160 -y 50 \ - "cd '$REPO_ROOT' && ${BUILD_ROOT_ENV_PREFIX}dotnet run --project MinecraftClient -c Release --no-build -- $MCC_ARGS_CMD; echo '=== MCC EXITED ==='; sleep 600" + "cd '$REPO_ROOT' && $MCC_LAUNCHER_CMD $MCC_ARGS_CMD; echo '=== MCC EXITED ==='; sleep 600" echo "" echo " TUI mode started in tmux session '$MCC_TMUX_SESSION'" echo " (TUI mode uses a real terminal; log file is not available, use MCC's /debug command)" @@ -241,7 +252,7 @@ elif $FILE_INPUT; then # FileInput mode: run in detached tmux, drive via session-specific input file tmux kill-session -t "$MCC_TMUX_SESSION" 2>/dev/null || true tmux new-session -d -s "$MCC_TMUX_SESSION" -x 160 -y 50 \ - "cd '$REPO_ROOT' && printf '%s\n' \"\$\$\" > '$PID_FILE' && exec env ${BUILD_ROOT_ENV_PREFIX}MCC_FILE_INPUT=1 MCC_INPUT_FILE='$INPUT_FILE' dotnet run --project MinecraftClient -c Release --no-build -- $MCC_ARGS_CMD > '$MCC_LOG' 2>&1" + "cd '$REPO_ROOT' && printf '%s\n' \"\$\$\" > '$PID_FILE' && exec env MCC_FILE_INPUT=1 MCC_INPUT_FILE='$INPUT_FILE' $MCC_LAUNCHER_CMD $MCC_ARGS_CMD > '$MCC_LOG' 2>&1" for _ in $(seq 1 25); do if [[ -s "$PID_FILE" ]]; then @@ -283,13 +294,12 @@ elif $FILE_INPUT; then echo " Attach (optional): tmux attach -t $MCC_TMUX_SESSION" echo " Stop MCC: echo 'quit' >> $INPUT_FILE" echo " Stop server: mc-stop $VERSION" - echo " shared servers stay up by default; rerun with --confirm only if you really need to stop it" echo "" else # Interactive classic mode: run in tmux (no pipe - ConsoleInteractive also needs tty) tmux kill-session -t "$MCC_TMUX_SESSION" 2>/dev/null || true tmux new-session -d -s "$MCC_TMUX_SESSION" -x 160 -y 50 \ - "cd '$REPO_ROOT' && ${BUILD_ROOT_ENV_PREFIX}dotnet run --project MinecraftClient -c Release --no-build -- $MCC_ARGS_CMD; echo '=== MCC EXITED ==='; sleep 600" + "cd '$REPO_ROOT' && $MCC_LAUNCHER_CMD $MCC_ARGS_CMD; echo '=== MCC EXITED ==='; sleep 600" echo "" echo " Classic mode started in tmux session '$MCC_TMUX_SESSION'" echo "" @@ -303,4 +313,3 @@ fi echo "Quick commands:" echo " mc-rcon 'op $USERNAME' # Give operator" echo " mc-rcon 'gamemode creative' # Creative mode" -echo " mc-stop $VERSION # shared server stays up by default; rerun with --confirm only when needed" diff --git a/tools/mcc-env.sh b/tools/mcc-env.sh index 437c5c9a0b..e05cb8a93c 100644 --- a/tools/mcc-env.sh +++ b/tools/mcc-env.sh @@ -121,6 +121,29 @@ _mcc_build_root() { printf '%s\n' "$MCC_REPO_ROOT" } +_mcc_runtime_output_dir() { + printf '%s/MinecraftClient/bin/Release/net10.0\n' "$(_mcc_build_root)" +} + +_mcc_runtime_app_path() { + local runtime_dir runtime_host runtime_dll + runtime_dir="$(_mcc_runtime_output_dir)" + runtime_host="$runtime_dir/MinecraftClient" + runtime_dll="$runtime_dir/MinecraftClient.dll" + + if [[ -x "$runtime_host" ]]; then + printf '%s\n' "$runtime_host" + return 0 + fi + + if [[ -f "$runtime_dll" ]]; then + printf '%s\n' "$runtime_dll" + return 0 + fi + + return 1 +} + _mcc_dotnet_env() { if [[ "${MCC_BUILD_MODE:-local}" == "tmpfs" ]]; then local build_root diff --git a/tools/pathing_contract_report.py b/tools/pathing_contract_report.py new file mode 100644 index 0000000000..caa711fdfb --- /dev/null +++ b/tools/pathing_contract_report.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import argparse +import json +import pathlib +import re +from dataclasses import dataclass + +SEGMENT_RE = re.compile( + r"\[PathMetric\] segmentComplete index=(?P\d+)" + r"(?: total=(?P\d+))? move=(?P\w+) ticks=(?P\d+)" +) +ROUTE_RE = re.compile( + r"\[PathMetric\] routeComplete totalTicks=(?P\d+)(?: replans=(?P\d+))?" +) +PLAN_RE = re.compile( + r"\[Navigate\]\s+seg\[(?P\d+)\] = (?P\w+): " + r"\((?P-?\d+),(?P-?\d+),(?P-?\d+)\)" +) + + +@dataclass(frozen=True) +class SegmentMetric: + index: int + move: str + ticks: int + + +@dataclass(frozen=True) +class PlannedSegment: + index: int + move: str + end_block: tuple[int, int, int] + + +@dataclass(frozen=True) +class MetricsReport: + segments: list[SegmentMetric] + total_ticks: int | None + replans: int | None + planned: list[PlannedSegment] + + +def load_json(path: pathlib.Path) -> dict[str, dict]: + data = json.loads(path.read_text(encoding="utf-8")) + return {entry["scenarioId"]: entry for entry in data} + + +def parse_metrics(text: str) -> MetricsReport: + segments = [ + SegmentMetric( + index=int(match["index"]), + move=match["move"], + ticks=int(match["ticks"]), + ) + for match in SEGMENT_RE.finditer(text) + ] + route_match = ROUTE_RE.search(text) + planned = [ + PlannedSegment( + index=int(match["index"]), + move=match["move"], + end_block=(int(match["x"]), int(match["y"]), int(match["z"])), + ) + for match in PLAN_RE.finditer(text) + ] + return MetricsReport( + segments=segments, + total_ticks=int(route_match["ticks"]) if route_match else None, + replans=int(route_match["replans"]) if route_match and route_match["replans"] else None, + planned=planned, + ) + + +def validate_report( + scenario_id: str, + report: MetricsReport, + planner_contract: dict, + timing_budget: dict, +) -> None: + if report.total_ticks is None: + raise SystemExit("Missing [PathMetric] routeComplete line") + + expected_planner_segments = planner_contract["segments"] + if len(report.planned) != len(expected_planner_segments): + raise SystemExit( + f"Planner contract mismatch for {scenario_id}: expected {len(expected_planner_segments)} " + f"segments, saw {len(report.planned)} planned segments" + ) + + for expected, actual in zip(expected_planner_segments, report.planned, strict=True): + expected_end = expected["endBlock"] + if actual.move != expected["moveType"] or actual.end_block != ( + expected_end["x"], + expected_end["y"], + expected_end["z"], + ): + raise SystemExit( + f"Planner contract mismatch for {scenario_id} segment {actual.index}: " + f"expected {expected['moveType']} -> ({expected_end['x']},{expected_end['y']},{expected_end['z']}), " + f"saw {actual.move} -> ({actual.end_block[0]},{actual.end_block[1]},{actual.end_block[2]})" + ) + + expected_timing_segments = timing_budget["segments"] + if len(report.segments) != len(expected_timing_segments): + raise SystemExit( + f"Timing contract mismatch for {scenario_id}: expected {len(expected_timing_segments)} " + f"segment metrics, saw {len(report.segments)}" + ) + + if report.total_ticks > timing_budget["maxTotalTicks"]: + raise SystemExit( + f"Route exceeded budget for {scenario_id}: actual={report.total_ticks} " + f"max={timing_budget['maxTotalTicks']}\n" + f"{render_report(scenario_id, report, timing_budget)}" + ) + + for expected, actual in zip(expected_timing_segments, report.segments, strict=True): + if actual.move != expected["moveType"]: + raise SystemExit( + f"Timing move mismatch for {scenario_id} segment {actual.index}: " + f"expected {expected['moveType']}, saw {actual.move}\n" + f"{render_report(scenario_id, report, timing_budget)}" + ) + if actual.ticks > expected["maxTicks"]: + raise SystemExit( + f"Segment {actual.index} slow for {scenario_id}: move={actual.move} " + f"actual={actual.ticks} max={expected['maxTicks']}\n" + f"{render_report(scenario_id, report, timing_budget)}" + ) + + +def render_report(scenario_id: str, report: MetricsReport, timing_budget: dict) -> str: + lines = [ + f"Route {scenario_id}: actual={report.total_ticks} " + f"expected={timing_budget['expectedTotalTicks']} max={timing_budget['maxTotalTicks']}" + ] + for expected, actual in zip(timing_budget["segments"], report.segments, strict=True): + delta = actual.ticks - expected["expectedTicks"] + lines.append( + f" seg[{actual.index}] move={actual.move} actual={actual.ticks} " + f"expected={expected['expectedTicks']} max={expected['maxTicks']} delta={delta:+d}" + ) + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--scenario-id", required=True) + parser.add_argument("--log-file", required=True) + parser.add_argument("--from-line", type=int, required=True) + parser.add_argument("--planner-contracts", required=True) + parser.add_argument("--timing-budgets", required=True) + args = parser.parse_args() + + log_path = pathlib.Path(args.log_file) + log_lines = log_path.read_text(encoding="utf-8", errors="ignore").splitlines() + text = "\n".join(log_lines[args.from_line :]) + + planner_contract = load_json(pathlib.Path(args.planner_contracts))[args.scenario_id] + timing_budget = load_json(pathlib.Path(args.timing_budgets))[args.scenario_id] + report = parse_metrics(text) + + validate_report(args.scenario_id, report, planner_contract, timing_budget) + print(render_report(args.scenario_id, report, timing_budget)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/pathing_data/canonical-live-cases.json b/tools/pathing_data/canonical-live-cases.json new file mode 100644 index 0000000000..af5a306cbb --- /dev/null +++ b/tools/pathing_data/canonical-live-cases.json @@ -0,0 +1,548 @@ +[ + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil3p0", + "bucket_id": "ceiling:headhitter:sprint:easy", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "ceiling-headhitter", + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 104.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil2p5", + "bucket_id": "ceiling:headhitter:sprint:boundary", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "ceiling-headhitter", + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 104.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil4p0", + "bucket_id": "ceiling:headhitter:sprint:reject", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "ceiling-headhitter", + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 102.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "linear-ascend-sprint-mm12-gap3-dy1p0", + "bucket_id": "linear:ascend:sprint:easy", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "linear-ascend", + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 104.0, + "y": 81.0, + "z": 100.0 + } + }, + { + "case_id": "linear-ascend-sprint-mm12-gap0-dy1p0", + "bucket_id": "linear:ascend:sprint:reject", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "linear-ascend", + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 81.0, + "z": 100.0 + } + }, + { + "case_id": "linear-descend-sprint-mm12-gap4-dym1p0", + "bucket_id": "linear:descend:sprint:easy", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "linear-descend", + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 105.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "linear-descend-sprint-mm12-gap5-dym1p0", + "bucket_id": "linear:descend:sprint:boundary", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "linear-descend", + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 106.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "linear-descend-sprint-mm12-gap0-dym1p0", + "bucket_id": "linear:descend:sprint:reject", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "linear-descend", + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "linear-flat-sprint-mm12-gap4-dy0p0", + "bucket_id": "linear:flat:sprint:easy", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "linear-flat", + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 105.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "linear-flat-sprint-mm12-gap0-dy0p0", + "bucket_id": "linear:flat:sprint:reject", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "linear-flat", + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "neo-neo-sprint-mm12-wall1", + "bucket_id": "neo:neo:sprint:easy", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "neo-wall", + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 102.0, + "y": 80.0, + "z": 101.0 + } + }, + { + "case_id": "neo-neo-sprint-mm12-wall4", + "bucket_id": "neo:neo:sprint:boundary", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "neo-wall", + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 102.0, + "y": 80.0, + "z": 104.0 + } + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap3-dy1p0-wo1", + "bucket_id": "sidewall:ascend:sprint:easy", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "sidewall-ascend", + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 104.0, + "y": 81.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap3-dy1p0-wo0", + "bucket_id": "sidewall:ascend:sprint:boundary", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "sidewall-ascend", + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 104.0, + "y": 81.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap0-dy1p0-wo0", + "bucket_id": "sidewall:ascend:sprint:reject", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "sidewall-ascend", + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 81.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap4-dym1p0-wo1", + "bucket_id": "sidewall:descend:sprint:easy", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "sidewall-descend", + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 105.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap5-dym1p0-wo0", + "bucket_id": "sidewall:descend:sprint:boundary", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "sidewall-descend", + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 106.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap0-dym1p0-wo0", + "bucket_id": "sidewall:descend:sprint:reject", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "sidewall-descend", + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 79.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap4-dy0p0-wo1", + "bucket_id": "sidewall:flat:sprint:easy", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "easy", + "expected_result": "pass", + "world_recipe_id": "sidewall-flat", + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 105.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap4-dy0p0-wo0", + "bucket_id": "sidewall:flat:sprint:boundary", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "boundary", + "expected_result": "pass", + "world_recipe_id": "sidewall-flat", + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 105.0, + "y": 80.0, + "z": 100.0 + } + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap0-dy0p0-wo0", + "bucket_id": "sidewall:flat:sprint:reject", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "difficulty_band": "reject", + "expected_result": "reject", + "world_recipe_id": "sidewall-flat", + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "start": { + "x": 100.5, + "y": 80.0, + "z": 100.5 + }, + "goal": { + "x": 101.0, + "y": 80.0, + "z": 100.0 + } + } +] diff --git a/tools/pathing_data/momentum-capabilities.json b/tools/pathing_data/momentum-capabilities.json new file mode 100644 index 0000000000..c922f44c21 --- /dev/null +++ b/tools/pathing_data/momentum-capabilities.json @@ -0,0 +1,1055 @@ +[ + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": null, + "delta_y": null, + "ceiling_height": 1.8125, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 1, + "delta_y": null, + "ceiling_height": 1.8125, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 12, + "max_reach": 1, + "delta_y": null, + "ceiling_height": 2.0, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 7, + "max_reach": 2, + "delta_y": null, + "ceiling_height": 2.5, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 8, + "max_mm": 12, + "max_reach": 3, + "delta_y": null, + "ceiling_height": 2.5, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 3, + "delta_y": null, + "ceiling_height": 3.0, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 4, + "delta_y": null, + "ceiling_height": 3.0, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 3, + "delta_y": null, + "ceiling_height": 4.0, + "wall_offset": null, + "notes": "" + }, + { + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": null, + "ceiling_height": 4.0, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 2, + "max_reach": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 3, + "max_mm": 12, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "capability_metric": "wall_width", + "min_mm": 0, + "max_mm": 1, + "max_reach": 3, + "delta_y": null, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "capability_metric": "wall_width", + "min_mm": 2, + "max_mm": 12, + "max_reach": 4, + "delta_y": null, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "capability_metric": "wall_width", + "min_mm": 0, + "max_mm": 0, + "max_reach": 1, + "delta_y": null, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "capability_metric": "wall_width", + "min_mm": 1, + "max_mm": 5, + "max_reach": 2, + "delta_y": null, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "capability_metric": "wall_width", + "min_mm": 6, + "max_mm": 12, + "max_reach": 3, + "delta_y": null, + "ceiling_height": null, + "wall_offset": null, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 2, + "max_reach": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 3, + "max_mm": 12, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 2, + "max_reach": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 2, + "max_reach": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 3, + "max_mm": 12, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 3, + "max_mm": 12, + "max_reach": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 0, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "capability_metric": "gap_blocks", + "min_mm": 1, + "max_mm": 12, + "max_reach": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 0, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 0, + "max_mm": 1, + "max_reach": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 1, + "notes": "" + }, + { + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "capability_metric": "gap_blocks", + "min_mm": 2, + "max_mm": 12, + "max_reach": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_offset": 2, + "notes": "" + } +] diff --git a/tools/pathing_data/momentum-capabilities.md b/tools/pathing_data/momentum-capabilities.md new file mode 100644 index 0000000000..d46eca9a17 --- /dev/null +++ b/tools/pathing_data/momentum-capabilities.md @@ -0,0 +1,71 @@ +# Momentum Capabilities + +This file compresses the full theory matrix into `mm` breakpoint bands that can +be consumed directly by the planner. + +| family | subfamily | movement_mode | qualifiers | mm_range | reach | +| --- | --- | --- | --- | --- | --- | +| ceiling | headhitter | sprint | ceil=1.8125 | 0..1 | max_gap=none | +| ceiling | headhitter | sprint | ceil=1.8125 | 2..12 | max_gap=1 | +| ceiling | headhitter | sprint | ceil=2.0 | 0..12 | max_gap=1 | +| ceiling | headhitter | sprint | ceil=2.5 | 0..7 | max_gap=2 | +| ceiling | headhitter | sprint | ceil=2.5 | 8..12 | max_gap=3 | +| ceiling | headhitter | sprint | ceil=3.0 | 0..1 | max_gap=3 | +| ceiling | headhitter | sprint | ceil=3.0 | 2..12 | max_gap=4 | +| ceiling | headhitter | sprint | ceil=4.0 | 0..0 | max_gap=3 | +| ceiling | headhitter | sprint | ceil=4.0 | 1..12 | max_gap=4 | +| linear | ascend | sprint | dy=1.0 | 0..0 | max_gap=2 | +| linear | ascend | sprint | dy=1.0 | 1..12 | max_gap=3 | +| linear | ascend | walk | dy=1.0 | 0..0 | max_gap=1 | +| linear | ascend | walk | dy=1.0 | 1..12 | max_gap=2 | +| linear | descend | sprint | dy=-2.0 | 0..0 | max_gap=4 | +| linear | descend | sprint | dy=-2.0 | 1..12 | max_gap=5 | +| linear | descend | sprint | dy=-1.0 | 0..1 | max_gap=4 | +| linear | descend | sprint | dy=-1.0 | 2..12 | max_gap=5 | +| linear | descend | walk | dy=-2.0 | 0..2 | max_gap=3 | +| linear | descend | walk | dy=-2.0 | 3..12 | max_gap=4 | +| linear | descend | walk | dy=-1.0 | 0..0 | max_gap=2 | +| linear | descend | walk | dy=-1.0 | 1..12 | max_gap=3 | +| linear | flat | sprint | dy=0.0 | 0..0 | max_gap=3 | +| linear | flat | sprint | dy=0.0 | 1..12 | max_gap=4 | +| linear | flat | walk | dy=0.0 | 0..1 | max_gap=2 | +| linear | flat | walk | dy=0.0 | 2..12 | max_gap=3 | +| neo | neo | sprint | - | 0..1 | max_wall_width=3 | +| neo | neo | sprint | - | 2..12 | max_wall_width=4 | +| neo | neo | walk | - | 0..0 | max_wall_width=1 | +| neo | neo | walk | - | 1..5 | max_wall_width=2 | +| neo | neo | walk | - | 6..12 | max_wall_width=3 | +| sidewall | ascend | sprint | dy=1.0, wo=0 | 0..0 | max_gap=2 | +| sidewall | ascend | sprint | dy=1.0, wo=0 | 1..12 | max_gap=3 | +| sidewall | ascend | sprint | dy=1.0, wo=1 | 0..0 | max_gap=2 | +| sidewall | ascend | sprint | dy=1.0, wo=1 | 1..12 | max_gap=3 | +| sidewall | ascend | walk | dy=1.0, wo=0 | 0..0 | max_gap=1 | +| sidewall | ascend | walk | dy=1.0, wo=0 | 1..12 | max_gap=2 | +| sidewall | ascend | walk | dy=1.0, wo=1 | 0..0 | max_gap=1 | +| sidewall | ascend | walk | dy=1.0, wo=1 | 1..12 | max_gap=2 | +| sidewall | descend | sprint | dy=-2.0, wo=0 | 0..0 | max_gap=4 | +| sidewall | descend | sprint | dy=-2.0, wo=0 | 1..12 | max_gap=5 | +| sidewall | descend | sprint | dy=-2.0, wo=1 | 0..0 | max_gap=4 | +| sidewall | descend | sprint | dy=-2.0, wo=1 | 1..12 | max_gap=5 | +| sidewall | descend | sprint | dy=-1.0, wo=0 | 0..1 | max_gap=4 | +| sidewall | descend | sprint | dy=-1.0, wo=0 | 2..12 | max_gap=5 | +| sidewall | descend | sprint | dy=-1.0, wo=1 | 0..1 | max_gap=4 | +| sidewall | descend | sprint | dy=-1.0, wo=1 | 2..12 | max_gap=5 | +| sidewall | descend | walk | dy=-2.0, wo=0 | 0..0 | max_gap=2 | +| sidewall | descend | walk | dy=-2.0, wo=0 | 1..2 | max_gap=3 | +| sidewall | descend | walk | dy=-2.0, wo=0 | 3..12 | max_gap=4 | +| sidewall | descend | walk | dy=-2.0, wo=1 | 0..0 | max_gap=2 | +| sidewall | descend | walk | dy=-2.0, wo=1 | 1..2 | max_gap=3 | +| sidewall | descend | walk | dy=-2.0, wo=1 | 3..12 | max_gap=4 | +| sidewall | descend | walk | dy=-1.0, wo=0 | 0..0 | max_gap=2 | +| sidewall | descend | walk | dy=-1.0, wo=0 | 1..12 | max_gap=3 | +| sidewall | descend | walk | dy=-1.0, wo=1 | 0..0 | max_gap=2 | +| sidewall | descend | walk | dy=-1.0, wo=1 | 1..12 | max_gap=3 | +| sidewall | flat | sprint | dy=0.0, wo=0 | 0..0 | max_gap=3 | +| sidewall | flat | sprint | dy=0.0, wo=0 | 1..12 | max_gap=4 | +| sidewall | flat | sprint | dy=0.0, wo=1 | 0..0 | max_gap=3 | +| sidewall | flat | sprint | dy=0.0, wo=1 | 1..12 | max_gap=4 | +| sidewall | flat | walk | dy=0.0, wo=0 | 0..1 | max_gap=2 | +| sidewall | flat | walk | dy=0.0, wo=0 | 2..12 | max_gap=3 | +| sidewall | flat | walk | dy=0.0, wo=1 | 0..1 | max_gap=2 | +| sidewall | flat | walk | dy=0.0, wo=1 | 2..12 | max_gap=3 | diff --git a/tools/pathing_data/theory-matrix.csv b/tools/pathing_data/theory-matrix.csv new file mode 100644 index 0000000000..6cbb59a1e4 --- /dev/null +++ b/tools/pathing_data/theory-matrix.csv @@ -0,0 +1,2861 @@ +case_id,family,subfamily,movement_mode,momentum_ticks,gap_blocks,delta_y,ceiling_height,wall_width,wall_offset,expected_reachable,landing_x,apex_y,margin,notes +linear-flat-walk-mm0-gap0-dy0p0,linear,flat,walk,0,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap0-dy1p0,linear,ascend,walk,0,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap0-dym1p0,linear,descend,walk,0,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap0-dym2p0,linear,descend,walk,0,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm0-gap1-dy0p0,linear,flat,walk,0,1,0.0,,,,True,2.517113405646343,1.2522033525119995,1.317113405646343, +linear-ascend-walk-mm0-gap1-dy1p0,linear,ascend,walk,0,1,1.0,,,,True,1.94476992399465,1.2522033525119995,0.7447699239946501, +linear-descend-walk-mm0-gap1-dym1p0,linear,descend,walk,0,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap1-dym2p0,linear,descend,walk,0,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm0-gap2-dy0p0,linear,flat,walk,0,2,0.0,,,,True,2.517113405646343,1.2522033525119995,0.3171134056463427, +linear-ascend-walk-mm0-gap2-dy1p0,linear,ascend,walk,0,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap2-dym1p0,linear,descend,walk,0,2,-1.0,,,,True,2.8958406866118747,1.2522033525119995,0.6958406866118745, +linear-descend-walk-mm0-gap2-dym2p0,linear,descend,walk,0,2,-2.0,,,,True,3.207960450907294,1.2522033525119995,1.007960450907294, +linear-flat-walk-mm0-gap3-dy0p0,linear,flat,walk,0,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap3-dy1p0,linear,ascend,walk,0,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap3-dym1p0,linear,descend,walk,0,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap3-dym2p0,linear,descend,walk,0,3,-2.0,,,,True,3.207960450907294,1.2522033525119995,0.007960450907293914, +linear-flat-walk-mm0-gap4-dy0p0,linear,flat,walk,0,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap4-dy1p0,linear,ascend,walk,0,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap4-dym1p0,linear,descend,walk,0,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap4-dym2p0,linear,descend,walk,0,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm0-gap5-dy0p0,linear,flat,walk,0,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap5-dy1p0,linear,ascend,walk,0,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap5-dym1p0,linear,descend,walk,0,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap5-dym2p0,linear,descend,walk,0,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm0-gap6-dy0p0,linear,flat,walk,0,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap6-dy1p0,linear,ascend,walk,0,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap6-dym1p0,linear,descend,walk,0,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap6-dym2p0,linear,descend,walk,0,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm0-gap7-dy0p0,linear,flat,walk,0,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm0-gap7-dy1p0,linear,ascend,walk,0,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap7-dym1p0,linear,descend,walk,0,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm0-gap7-dym2p0,linear,descend,walk,0,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap0-dy0p0,linear,flat,walk,1,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap0-dy1p0,linear,ascend,walk,1,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap0-dym1p0,linear,descend,walk,1,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap0-dym2p0,linear,descend,walk,1,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap1-dy0p0,linear,flat,walk,1,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap1-dy1p0,linear,ascend,walk,1,1,1.0,,,,True,2.366298502068217,1.2522033525119995,1.1662985020682168, +linear-descend-walk-mm1-gap1-dym1p0,linear,descend,walk,1,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap1-dym2p0,linear,descend,walk,1,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap2-dy0p0,linear,flat,walk,1,2,0.0,,,,True,3.0333486332242363,1.2522033525119995,0.8333486332242361, +linear-ascend-walk-mm1-gap2-dy1p0,linear,ascend,walk,1,2,1.0,,,,True,2.366298502068217,1.2522033525119995,0.1662985020682166, +linear-descend-walk-mm1-gap2-dym1p0,linear,descend,walk,1,2,-1.0,,,,True,3.459075120299828,1.2522033525119995,1.259075120299828, +linear-descend-walk-mm1-gap2-dym2p0,linear,descend,walk,1,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap3-dy0p0,linear,flat,walk,1,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap3-dy1p0,linear,ascend,walk,1,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap3-dym1p0,linear,descend,walk,1,3,-1.0,,,,True,3.459075120299828,1.2522033525119995,0.25907512029982804, +linear-descend-walk-mm1-gap3-dym2p0,linear,descend,walk,1,3,-2.0,,,,True,3.8031629073028737,1.2522033525119995,0.6031629073028735, +linear-flat-walk-mm1-gap4-dy0p0,linear,flat,walk,1,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap4-dy1p0,linear,ascend,walk,1,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap4-dym1p0,linear,descend,walk,1,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap4-dym2p0,linear,descend,walk,1,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap5-dy0p0,linear,flat,walk,1,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap5-dy1p0,linear,ascend,walk,1,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap5-dym1p0,linear,descend,walk,1,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap5-dym2p0,linear,descend,walk,1,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap6-dy0p0,linear,flat,walk,1,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap6-dy1p0,linear,ascend,walk,1,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap6-dym1p0,linear,descend,walk,1,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap6-dym2p0,linear,descend,walk,1,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm1-gap7-dy0p0,linear,flat,walk,1,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm1-gap7-dy1p0,linear,ascend,walk,1,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap7-dym1p0,linear,descend,walk,1,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm1-gap7-dym2p0,linear,descend,walk,1,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap0-dy0p0,linear,flat,walk,2,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap0-dy1p0,linear,ascend,walk,2,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap0-dym1p0,linear,descend,walk,2,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap0-dym2p0,linear,descend,walk,2,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap1-dy0p0,linear,flat,walk,2,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap1-dy1p0,linear,ascend,walk,2,1,1.0,,,,True,2.596453105696384,1.2522033525119995,1.396453105696384, +linear-descend-walk-mm2-gap1-dym1p0,linear,descend,walk,2,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap1-dym2p0,linear,descend,walk,2,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap2-dy0p0,linear,flat,walk,2,2,0.0,,,,True,3.315213067481766,1.2522033525119995,1.1152130674817657, +linear-ascend-walk-mm2-gap2-dy1p0,linear,ascend,walk,2,2,1.0,,,,True,2.596453105696384,1.2522033525119995,0.3964531056963838, +linear-descend-walk-mm2-gap2-dym1p0,linear,descend,walk,2,2,-1.0,,,,True,3.7666011210934505,1.2522033525119995,1.5666011210934503, +linear-descend-walk-mm2-gap2-dym2p0,linear,descend,walk,2,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap3-dy0p0,linear,flat,walk,2,3,0.0,,,,True,3.315213067481766,1.2522033525119995,0.11521306748176574, +linear-ascend-walk-mm2-gap3-dy1p0,linear,ascend,walk,2,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap3-dym1p0,linear,descend,walk,2,3,-1.0,,,,True,3.7666011210934505,1.2522033525119995,0.5666011210934503, +linear-descend-walk-mm2-gap3-dym2p0,linear,descend,walk,2,3,-2.0,,,,True,4.12814344849486,1.2522033525119995,0.9281434484948594, +linear-flat-walk-mm2-gap4-dy0p0,linear,flat,walk,2,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap4-dy1p0,linear,ascend,walk,2,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap4-dym1p0,linear,descend,walk,2,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap4-dym2p0,linear,descend,walk,2,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap5-dy0p0,linear,flat,walk,2,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap5-dy1p0,linear,ascend,walk,2,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap5-dym1p0,linear,descend,walk,2,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap5-dym2p0,linear,descend,walk,2,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap6-dy0p0,linear,flat,walk,2,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap6-dy1p0,linear,ascend,walk,2,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap6-dym1p0,linear,descend,walk,2,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap6-dym2p0,linear,descend,walk,2,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm2-gap7-dy0p0,linear,flat,walk,2,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm2-gap7-dy1p0,linear,ascend,walk,2,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap7-dym1p0,linear,descend,walk,2,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm2-gap7-dym2p0,linear,descend,walk,2,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap0-dy0p0,linear,flat,walk,3,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap0-dy1p0,linear,ascend,walk,3,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap0-dym1p0,linear,descend,walk,3,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap0-dym2p0,linear,descend,walk,3,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap1-dy0p0,linear,flat,walk,3,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap1-dy1p0,linear,ascend,walk,3,1,1.0,,,,True,2.7221175192773623,1.2522033525119995,1.5221175192773624, +linear-descend-walk-mm3-gap1-dym1p0,linear,descend,walk,3,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap1-dym2p0,linear,descend,walk,3,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap2-dy0p0,linear,flat,walk,3,2,0.0,,,,True,3.469111048586376,1.2522033525119995,1.269111048586376, +linear-ascend-walk-mm3-gap2-dy1p0,linear,ascend,walk,3,2,1.0,,,,True,2.7221175192773623,1.2522033525119995,0.5221175192773622, +linear-descend-walk-mm3-gap2-dym1p0,linear,descend,walk,3,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap2-dym2p0,linear,descend,walk,3,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap3-dy0p0,linear,flat,walk,3,3,0.0,,,,True,3.469111048586376,1.2522033525119995,0.2691110485863759, +linear-ascend-walk-mm3-gap3-dy1p0,linear,ascend,walk,3,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap3-dym1p0,linear,descend,walk,3,3,-1.0,,,,True,3.9345103175267675,1.2522033525119995,0.7345103175267673, +linear-descend-walk-mm3-gap3-dym2p0,linear,descend,walk,3,3,-2.0,,,,True,4.3055828239856835,1.2522033525119995,1.1055828239856833, +linear-flat-walk-mm3-gap4-dy0p0,linear,flat,walk,3,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap4-dy1p0,linear,ascend,walk,3,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap4-dym1p0,linear,descend,walk,3,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap4-dym2p0,linear,descend,walk,3,4,-2.0,,,,True,4.3055828239856835,1.2522033525119995,0.1055828239856833, +linear-flat-walk-mm3-gap5-dy0p0,linear,flat,walk,3,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap5-dy1p0,linear,ascend,walk,3,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap5-dym1p0,linear,descend,walk,3,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap5-dym2p0,linear,descend,walk,3,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap6-dy0p0,linear,flat,walk,3,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap6-dy1p0,linear,ascend,walk,3,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap6-dym1p0,linear,descend,walk,3,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap6-dym2p0,linear,descend,walk,3,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm3-gap7-dy0p0,linear,flat,walk,3,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm3-gap7-dy1p0,linear,ascend,walk,3,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap7-dym1p0,linear,descend,walk,3,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm3-gap7-dym2p0,linear,descend,walk,3,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap0-dy0p0,linear,flat,walk,4,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap0-dy1p0,linear,ascend,walk,4,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap0-dym1p0,linear,descend,walk,4,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap0-dym2p0,linear,descend,walk,4,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap1-dy0p0,linear,flat,walk,4,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap1-dy1p0,linear,ascend,walk,4,1,1.0,,,,True,2.790730289092578,1.2522033525119995,1.5907302890925779, +linear-descend-walk-mm4-gap1-dym1p0,linear,descend,walk,4,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap1-dym2p0,linear,descend,walk,4,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap2-dy0p0,linear,flat,walk,4,2,0.0,,,,True,3.5531393462694942,1.2522033525119995,1.353139346269494, +linear-ascend-walk-mm4-gap2-dy1p0,linear,ascend,walk,4,2,1.0,,,,True,2.790730289092578,1.2522033525119995,0.5907302890925776, +linear-descend-walk-mm4-gap2-dym1p0,linear,descend,walk,4,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap2-dym2p0,linear,descend,walk,4,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap3-dy0p0,linear,flat,walk,4,3,0.0,,,,True,3.5531393462694942,1.2522033525119995,0.35313934626949406, +linear-ascend-walk-mm4-gap3-dy1p0,linear,ascend,walk,4,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap3-dym1p0,linear,descend,walk,4,3,-1.0,,,,True,4.02618873877936,1.2522033525119995,0.8261887387793596, +linear-descend-walk-mm4-gap3-dym2p0,linear,descend,walk,4,3,-2.0,,,,True,4.402464723003673,1.2522033525119995,1.202464723003673, +linear-flat-walk-mm4-gap4-dy0p0,linear,flat,walk,4,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap4-dy1p0,linear,ascend,walk,4,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap4-dym1p0,linear,descend,walk,4,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap4-dym2p0,linear,descend,walk,4,4,-2.0,,,,True,4.402464723003673,1.2522033525119995,0.2024647230036729, +linear-flat-walk-mm4-gap5-dy0p0,linear,flat,walk,4,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap5-dy1p0,linear,ascend,walk,4,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap5-dym1p0,linear,descend,walk,4,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap5-dym2p0,linear,descend,walk,4,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap6-dy0p0,linear,flat,walk,4,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap6-dy1p0,linear,ascend,walk,4,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap6-dym1p0,linear,descend,walk,4,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap6-dym2p0,linear,descend,walk,4,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm4-gap7-dy0p0,linear,flat,walk,4,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm4-gap7-dy1p0,linear,ascend,walk,4,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap7-dym1p0,linear,descend,walk,4,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm4-gap7-dym2p0,linear,descend,walk,4,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap0-dy0p0,linear,flat,walk,5,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap0-dy1p0,linear,ascend,walk,5,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap0-dym1p0,linear,descend,walk,5,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap0-dym2p0,linear,descend,walk,5,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap1-dy0p0,linear,flat,walk,5,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap1-dy1p0,linear,ascend,walk,5,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap1-dym1p0,linear,descend,walk,5,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap1-dym2p0,linear,descend,walk,5,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap2-dy0p0,linear,flat,walk,5,2,0.0,,,,True,3.599018796804477,1.2522033525119995,1.399018796804477, +linear-ascend-walk-mm5-gap2-dy1p0,linear,ascend,walk,5,2,1.0,,,,True,2.828192861411685,1.2522033525119995,0.628192861411685, +linear-descend-walk-mm5-gap2-dym1p0,linear,descend,walk,5,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap2-dym2p0,linear,descend,walk,5,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap3-dy0p0,linear,flat,walk,5,3,0.0,,,,True,3.599018796804477,1.2522033525119995,0.39901879680447694, +linear-ascend-walk-mm5-gap3-dy1p0,linear,ascend,walk,5,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap3-dym1p0,linear,descend,walk,5,3,-1.0,,,,True,4.076245156783275,1.2522033525119995,0.8762451567832752, +linear-descend-walk-mm5-gap3-dym2p0,linear,descend,walk,5,3,-2.0,,,,True,4.4553622398674975,1.2522033525119995,1.2553622398674973, +linear-flat-walk-mm5-gap4-dy0p0,linear,flat,walk,5,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap4-dy1p0,linear,ascend,walk,5,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap4-dym1p0,linear,descend,walk,5,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap4-dym2p0,linear,descend,walk,5,4,-2.0,,,,True,4.4553622398674975,1.2522033525119995,0.2553622398674973, +linear-flat-walk-mm5-gap5-dy0p0,linear,flat,walk,5,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap5-dy1p0,linear,ascend,walk,5,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap5-dym1p0,linear,descend,walk,5,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap5-dym2p0,linear,descend,walk,5,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap6-dy0p0,linear,flat,walk,5,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap6-dy1p0,linear,ascend,walk,5,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap6-dym1p0,linear,descend,walk,5,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap6-dym2p0,linear,descend,walk,5,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm5-gap7-dy0p0,linear,flat,walk,5,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm5-gap7-dy1p0,linear,ascend,walk,5,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap7-dym1p0,linear,descend,walk,5,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm5-gap7-dym2p0,linear,descend,walk,5,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap0-dy0p0,linear,flat,walk,6,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap0-dy1p0,linear,ascend,walk,6,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap0-dym1p0,linear,descend,walk,6,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap0-dym2p0,linear,descend,walk,6,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap1-dy0p0,linear,flat,walk,6,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap1-dy1p0,linear,ascend,walk,6,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap1-dym1p0,linear,descend,walk,6,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap1-dym2p0,linear,descend,walk,6,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap2-dy0p0,linear,flat,walk,6,2,0.0,,,,True,3.624068976796577,1.2522033525119995,1.4240689767965766, +linear-ascend-walk-mm6-gap2-dy1p0,linear,ascend,walk,6,2,1.0,,,,True,2.848647425897918,1.2522033525119995,0.6486474258979178, +linear-descend-walk-mm6-gap2-dym1p0,linear,descend,walk,6,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap2-dym2p0,linear,descend,walk,6,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap3-dy0p0,linear,flat,walk,6,3,0.0,,,,True,3.624068976796577,1.2522033525119995,0.4240689767965766, +linear-ascend-walk-mm6-gap3-dy1p0,linear,ascend,walk,6,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap3-dym1p0,linear,descend,walk,6,3,-1.0,,,,True,4.103575961013412,1.2522033525119995,0.903575961013412, +linear-descend-walk-mm6-gap3-dym2p0,linear,descend,walk,6,3,-2.0,,,,True,4.484244284075144,1.2522033525119995,1.284244284075144, +linear-flat-walk-mm6-gap4-dy0p0,linear,flat,walk,6,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap4-dy1p0,linear,ascend,walk,6,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap4-dym1p0,linear,descend,walk,6,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap4-dym2p0,linear,descend,walk,6,4,-2.0,,,,True,4.484244284075144,1.2522033525119995,0.284244284075144, +linear-flat-walk-mm6-gap5-dy0p0,linear,flat,walk,6,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap5-dy1p0,linear,ascend,walk,6,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap5-dym1p0,linear,descend,walk,6,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap5-dym2p0,linear,descend,walk,6,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap6-dy0p0,linear,flat,walk,6,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap6-dy1p0,linear,ascend,walk,6,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap6-dym1p0,linear,descend,walk,6,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap6-dym2p0,linear,descend,walk,6,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm6-gap7-dy0p0,linear,flat,walk,6,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm6-gap7-dy1p0,linear,ascend,walk,6,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap7-dym1p0,linear,descend,walk,6,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm6-gap7-dym2p0,linear,descend,walk,6,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap0-dy0p0,linear,flat,walk,7,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap0-dy1p0,linear,ascend,walk,7,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap0-dym1p0,linear,descend,walk,7,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap0-dym2p0,linear,descend,walk,7,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap1-dy0p0,linear,flat,walk,7,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap1-dy1p0,linear,ascend,walk,7,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap1-dym1p0,linear,descend,walk,7,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap1-dym2p0,linear,descend,walk,7,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap2-dy0p0,linear,flat,walk,7,2,0.0,,,,True,3.6377463750722634,1.2522033525119995,1.4377463750722632, +linear-ascend-walk-mm7-gap2-dy1p0,linear,ascend,walk,7,2,1.0,,,,True,2.8598156181074006,1.2522033525119995,0.6598156181074004, +linear-descend-walk-mm7-gap2-dym1p0,linear,descend,walk,7,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap2-dym2p0,linear,descend,walk,7,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap3-dy0p0,linear,flat,walk,7,3,0.0,,,,True,3.6377463750722634,1.2522033525119995,0.4377463750722632, +linear-ascend-walk-mm7-gap3-dy1p0,linear,ascend,walk,7,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap3-dym1p0,linear,descend,walk,7,3,-1.0,,,,True,4.118498580123067,1.2522033525119995,0.9184985801230665, +linear-descend-walk-mm7-gap3-dym2p0,linear,descend,walk,7,3,-2.0,,,,True,4.500013880212519,1.2522033525119995,1.3000138802125187, +linear-flat-walk-mm7-gap4-dy0p0,linear,flat,walk,7,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap4-dy1p0,linear,ascend,walk,7,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap4-dym1p0,linear,descend,walk,7,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap4-dym2p0,linear,descend,walk,7,4,-2.0,,,,True,4.500013880212519,1.2522033525119995,0.30001388021251874, +linear-flat-walk-mm7-gap5-dy0p0,linear,flat,walk,7,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap5-dy1p0,linear,ascend,walk,7,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap5-dym1p0,linear,descend,walk,7,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap5-dym2p0,linear,descend,walk,7,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap6-dy0p0,linear,flat,walk,7,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap6-dy1p0,linear,ascend,walk,7,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap6-dym1p0,linear,descend,walk,7,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap6-dym2p0,linear,descend,walk,7,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm7-gap7-dy0p0,linear,flat,walk,7,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm7-gap7-dy1p0,linear,ascend,walk,7,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap7-dym1p0,linear,descend,walk,7,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm7-gap7-dym2p0,linear,descend,walk,7,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap0-dy0p0,linear,flat,walk,8,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap0-dy1p0,linear,ascend,walk,8,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap0-dym1p0,linear,descend,walk,8,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap0-dym2p0,linear,descend,walk,8,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap1-dy0p0,linear,flat,walk,8,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap1-dy1p0,linear,ascend,walk,8,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap1-dym1p0,linear,descend,walk,8,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap1-dym2p0,linear,descend,walk,8,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap2-dy0p0,linear,flat,walk,8,2,0.0,,,,True,3.6452142345307887,1.2522033525119995,1.4452142345307886, +linear-ascend-walk-mm8-gap2-dy1p0,linear,ascend,walk,8,2,1.0,,,,True,2.865913451053779,1.2522033525119995,0.665913451053779, +linear-descend-walk-mm8-gap2-dym1p0,linear,descend,walk,8,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap2-dym2p0,linear,descend,walk,8,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap3-dy0p0,linear,flat,walk,8,3,0.0,,,,True,3.6452142345307887,1.2522033525119995,0.44521423453078857, +linear-ascend-walk-mm8-gap3-dy1p0,linear,ascend,walk,8,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap3-dym1p0,linear,descend,walk,8,3,-1.0,,,,True,4.126646330156939,1.2522033525119995,0.926646330156939, +linear-descend-walk-mm8-gap3-dym2p0,linear,descend,walk,8,3,-2.0,,,,True,4.508624079703527,1.2522033525119995,1.3086240797035265, +linear-flat-walk-mm8-gap4-dy0p0,linear,flat,walk,8,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap4-dy1p0,linear,ascend,walk,8,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap4-dym1p0,linear,descend,walk,8,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap4-dym2p0,linear,descend,walk,8,4,-2.0,,,,True,4.508624079703527,1.2522033525119995,0.30862407970352645, +linear-flat-walk-mm8-gap5-dy0p0,linear,flat,walk,8,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap5-dy1p0,linear,ascend,walk,8,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap5-dym1p0,linear,descend,walk,8,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap5-dym2p0,linear,descend,walk,8,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap6-dy0p0,linear,flat,walk,8,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap6-dy1p0,linear,ascend,walk,8,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap6-dym1p0,linear,descend,walk,8,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap6-dym2p0,linear,descend,walk,8,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm8-gap7-dy0p0,linear,flat,walk,8,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm8-gap7-dy1p0,linear,ascend,walk,8,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap7-dym1p0,linear,descend,walk,8,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm8-gap7-dym2p0,linear,descend,walk,8,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap0-dy0p0,linear,flat,walk,9,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap0-dy1p0,linear,ascend,walk,9,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap0-dym1p0,linear,descend,walk,9,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap0-dym2p0,linear,descend,walk,9,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap1-dy0p0,linear,flat,walk,9,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap1-dy1p0,linear,ascend,walk,9,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap1-dym1p0,linear,descend,walk,9,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap1-dym2p0,linear,descend,walk,9,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap2-dy0p0,linear,flat,walk,9,2,0.0,,,,True,3.6492916857951427,1.2522033525119995,1.4492916857951426, +linear-ascend-walk-mm9-gap2-dy1p0,linear,ascend,walk,9,2,1.0,,,,True,2.8692428678425004,1.2522033525119995,0.6692428678425002, +linear-descend-walk-mm9-gap2-dym1p0,linear,descend,walk,9,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap2-dym2p0,linear,descend,walk,9,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap3-dy0p0,linear,flat,walk,9,3,0.0,,,,True,3.6492916857951427,1.2522033525119995,0.44929168579514256, +linear-ascend-walk-mm9-gap3-dy1p0,linear,ascend,walk,9,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap3-dym1p0,linear,descend,walk,9,3,-1.0,,,,True,4.131095001675432,1.2522033525119995,0.9310950016754322, +linear-descend-walk-mm9-gap3-dym2p0,linear,descend,walk,9,3,-2.0,,,,True,4.513325248625614,1.2522033525119995,1.3133252486256142, +linear-flat-walk-mm9-gap4-dy0p0,linear,flat,walk,9,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap4-dy1p0,linear,ascend,walk,9,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap4-dym1p0,linear,descend,walk,9,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap4-dym2p0,linear,descend,walk,9,4,-2.0,,,,True,4.513325248625614,1.2522033525119995,0.31332524862561417, +linear-flat-walk-mm9-gap5-dy0p0,linear,flat,walk,9,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap5-dy1p0,linear,ascend,walk,9,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap5-dym1p0,linear,descend,walk,9,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap5-dym2p0,linear,descend,walk,9,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap6-dy0p0,linear,flat,walk,9,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap6-dy1p0,linear,ascend,walk,9,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap6-dym1p0,linear,descend,walk,9,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap6-dym2p0,linear,descend,walk,9,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm9-gap7-dy0p0,linear,flat,walk,9,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm9-gap7-dy1p0,linear,ascend,walk,9,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap7-dym1p0,linear,descend,walk,9,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm9-gap7-dym2p0,linear,descend,walk,9,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap0-dy0p0,linear,flat,walk,10,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap0-dy1p0,linear,ascend,walk,10,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap0-dym1p0,linear,descend,walk,10,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap0-dym2p0,linear,descend,walk,10,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap1-dy0p0,linear,flat,walk,10,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap1-dy1p0,linear,ascend,walk,10,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap1-dym1p0,linear,descend,walk,10,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap1-dym2p0,linear,descend,walk,10,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap2-dy0p0,linear,flat,walk,10,2,0.0,,,,True,3.651517974185481,1.2522033525119995,1.4515179741854807, +linear-ascend-walk-mm10-gap2-dy1p0,linear,ascend,walk,10,2,1.0,,,,True,2.8710607294091433,1.2522033525119995,0.6710607294091431, +linear-descend-walk-mm10-gap2-dym1p0,linear,descend,walk,10,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap2-dym2p0,linear,descend,walk,10,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap3-dy0p0,linear,flat,walk,10,3,0.0,,,,True,3.651517974185481,1.2522033525119995,0.4515179741854807, +linear-ascend-walk-mm10-gap3-dy1p0,linear,ascend,walk,10,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap3-dym1p0,linear,descend,walk,10,3,-1.0,,,,True,4.13352397632453,1.2522033525119995,0.9335239763245298, +linear-descend-walk-mm10-gap3-dym2p0,linear,descend,walk,10,3,-2.0,,,,True,4.515892086857076,1.2522033525119995,1.315892086857076, +linear-flat-walk-mm10-gap4-dy0p0,linear,flat,walk,10,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap4-dy1p0,linear,ascend,walk,10,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap4-dym1p0,linear,descend,walk,10,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap4-dym2p0,linear,descend,walk,10,4,-2.0,,,,True,4.515892086857076,1.2522033525119995,0.31589208685707604, +linear-flat-walk-mm10-gap5-dy0p0,linear,flat,walk,10,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap5-dy1p0,linear,ascend,walk,10,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap5-dym1p0,linear,descend,walk,10,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap5-dym2p0,linear,descend,walk,10,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap6-dy0p0,linear,flat,walk,10,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap6-dy1p0,linear,ascend,walk,10,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap6-dym1p0,linear,descend,walk,10,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap6-dym2p0,linear,descend,walk,10,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm10-gap7-dy0p0,linear,flat,walk,10,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm10-gap7-dy1p0,linear,ascend,walk,10,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap7-dym1p0,linear,descend,walk,10,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm10-gap7-dym2p0,linear,descend,walk,10,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap0-dy0p0,linear,flat,walk,11,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap0-dy1p0,linear,ascend,walk,11,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap0-dym1p0,linear,descend,walk,11,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap0-dym2p0,linear,descend,walk,11,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap1-dy0p0,linear,flat,walk,11,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap1-dy1p0,linear,ascend,walk,11,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap1-dym1p0,linear,descend,walk,11,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap1-dym2p0,linear,descend,walk,11,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap2-dy0p0,linear,flat,walk,11,2,0.0,,,,True,3.652733527646605,1.2522033525119995,1.452733527646605, +linear-ascend-walk-mm11-gap2-dy1p0,linear,ascend,walk,11,2,1.0,,,,True,2.8720532818245297,1.2522033525119995,0.6720532818245295, +linear-descend-walk-mm11-gap2-dym1p0,linear,descend,walk,11,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap2-dym2p0,linear,descend,walk,11,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap3-dy0p0,linear,flat,walk,11,3,0.0,,,,True,3.652733527646605,1.2522033525119995,0.4527335276466049, +linear-ascend-walk-mm11-gap3-dy1p0,linear,ascend,walk,11,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap3-dym1p0,linear,descend,walk,11,3,-1.0,,,,True,4.1348501964829385,1.2522033525119995,0.9348501964829383, +linear-descend-walk-mm11-gap3-dym2p0,linear,descend,walk,11,3,-2.0,,,,True,4.517293580531455,1.2522033525119995,1.3172935805314552, +linear-flat-walk-mm11-gap4-dy0p0,linear,flat,walk,11,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap4-dy1p0,linear,ascend,walk,11,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap4-dym1p0,linear,descend,walk,11,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap4-dym2p0,linear,descend,walk,11,4,-2.0,,,,True,4.517293580531455,1.2522033525119995,0.3172935805314552, +linear-flat-walk-mm11-gap5-dy0p0,linear,flat,walk,11,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap5-dy1p0,linear,ascend,walk,11,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap5-dym1p0,linear,descend,walk,11,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap5-dym2p0,linear,descend,walk,11,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap6-dy0p0,linear,flat,walk,11,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap6-dy1p0,linear,ascend,walk,11,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap6-dym1p0,linear,descend,walk,11,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap6-dym2p0,linear,descend,walk,11,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm11-gap7-dy0p0,linear,flat,walk,11,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm11-gap7-dy1p0,linear,ascend,walk,11,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap7-dym1p0,linear,descend,walk,11,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm11-gap7-dym2p0,linear,descend,walk,11,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap0-dy0p0,linear,flat,walk,12,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap0-dy1p0,linear,ascend,walk,12,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap0-dym1p0,linear,descend,walk,12,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap0-dym2p0,linear,descend,walk,12,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap1-dy0p0,linear,flat,walk,12,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap1-dy1p0,linear,ascend,walk,12,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap1-dym1p0,linear,descend,walk,12,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap1-dym2p0,linear,descend,walk,12,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap2-dy0p0,linear,flat,walk,12,2,0.0,,,,True,3.6533972198363784,1.2522033525119995,1.4533972198363783, +linear-ascend-walk-mm12-gap2-dy1p0,linear,ascend,walk,12,2,1.0,,,,True,2.8725952154433307,1.2522033525119995,0.6725952154433306, +linear-descend-walk-mm12-gap2-dym1p0,linear,descend,walk,12,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap2-dym2p0,linear,descend,walk,12,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap3-dy0p0,linear,flat,walk,12,3,0.0,,,,True,3.6533972198363784,1.2522033525119995,0.45339721983637826, +linear-ascend-walk-mm12-gap3-dy1p0,linear,ascend,walk,12,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap3-dym1p0,linear,descend,walk,12,3,-1.0,,,,True,4.135574312689427,1.2522033525119995,0.9355743126894271, +linear-descend-walk-mm12-gap3-dym2p0,linear,descend,walk,12,3,-2.0,,,,True,4.518058796077664,1.2522033525119995,1.318058796077664, +linear-flat-walk-mm12-gap4-dy0p0,linear,flat,walk,12,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap4-dy1p0,linear,ascend,walk,12,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap4-dym1p0,linear,descend,walk,12,4,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap4-dym2p0,linear,descend,walk,12,4,-2.0,,,,True,4.518058796077664,1.2522033525119995,0.31805879607766396, +linear-flat-walk-mm12-gap5-dy0p0,linear,flat,walk,12,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap5-dy1p0,linear,ascend,walk,12,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap5-dym1p0,linear,descend,walk,12,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap5-dym2p0,linear,descend,walk,12,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap6-dy0p0,linear,flat,walk,12,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap6-dy1p0,linear,ascend,walk,12,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap6-dym1p0,linear,descend,walk,12,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap6-dym2p0,linear,descend,walk,12,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-walk-mm12-gap7-dy0p0,linear,flat,walk,12,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-walk-mm12-gap7-dy1p0,linear,ascend,walk,12,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap7-dym1p0,linear,descend,walk,12,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-walk-mm12-gap7-dym2p0,linear,descend,walk,12,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap0-dy0p0,linear,flat,sprint,0,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap0-dy1p0,linear,ascend,sprint,0,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap0-dym1p0,linear,descend,sprint,0,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap0-dym2p0,linear,descend,sprint,0,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap1-dy0p0,linear,flat,sprint,0,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap1-dy1p0,linear,ascend,sprint,0,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap1-dym1p0,linear,descend,sprint,0,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap1-dym2p0,linear,descend,sprint,0,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap2-dy0p0,linear,flat,sprint,0,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap2-dy1p0,linear,ascend,sprint,0,2,1.0,,,,True,3.132075308966179,1.2522033525119995,0.9320753089661786, +linear-descend-sprint-mm0-gap2-dym1p0,linear,descend,sprint,0,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap2-dym2p0,linear,descend,sprint,0,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap3-dy0p0,linear,flat,sprint,0,3,0.0,,,,True,3.971175828688666,1.2522033525119995,0.7711758286886656, +linear-ascend-sprint-mm0-gap3-dy1p0,linear,ascend,sprint,0,3,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap3-dym1p0,linear,descend,sprint,0,3,-1.0,,,,True,4.4822841946066925,1.2522033525119995,1.2822841946066923, +linear-descend-sprint-mm0-gap3-dym2p0,linear,descend,sprint,0,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap4-dy0p0,linear,flat,sprint,0,4,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap4-dy1p0,linear,ascend,sprint,0,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap4-dym1p0,linear,descend,sprint,0,4,-1.0,,,,True,4.4822841946066925,1.2522033525119995,0.2822841946066923, +linear-descend-sprint-mm0-gap4-dym2p0,linear,descend,sprint,0,4,-2.0,,,,True,4.884447214524587,1.2522033525119995,0.684447214524587, +linear-flat-sprint-mm0-gap5-dy0p0,linear,flat,sprint,0,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap5-dy1p0,linear,ascend,sprint,0,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap5-dym1p0,linear,descend,sprint,0,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap5-dym2p0,linear,descend,sprint,0,5,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap6-dy0p0,linear,flat,sprint,0,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap6-dy1p0,linear,ascend,sprint,0,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap6-dym1p0,linear,descend,sprint,0,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap6-dym2p0,linear,descend,sprint,0,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm0-gap7-dy0p0,linear,flat,sprint,0,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm0-gap7-dy1p0,linear,ascend,sprint,0,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap7-dym1p0,linear,descend,sprint,0,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm0-gap7-dym2p0,linear,descend,sprint,0,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap0-dy0p0,linear,flat,sprint,1,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap0-dy1p0,linear,ascend,sprint,1,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap0-dym1p0,linear,descend,sprint,1,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap0-dym2p0,linear,descend,sprint,1,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap1-dy0p0,linear,flat,sprint,1,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap1-dy1p0,linear,ascend,sprint,1,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap1-dym1p0,linear,descend,sprint,1,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap1-dym2p0,linear,descend,sprint,1,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap2-dy0p0,linear,flat,sprint,1,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap2-dy1p0,linear,ascend,sprint,1,2,1.0,,,,True,3.553603887039745,1.2522033525119995,1.3536038870397449, +linear-descend-sprint-mm1-gap2-dym1p0,linear,descend,sprint,1,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap2-dym2p0,linear,descend,sprint,1,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap3-dy0p0,linear,flat,sprint,1,3,0.0,,,,True,4.4874110562665575,1.2522033525119995,1.2874110562665573, +linear-ascend-sprint-mm1-gap3-dy1p0,linear,ascend,sprint,1,3,1.0,,,,True,3.553603887039745,1.2522033525119995,0.3536038870397449, +linear-descend-sprint-mm1-gap3-dym1p0,linear,descend,sprint,1,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap3-dym2p0,linear,descend,sprint,1,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap4-dy0p0,linear,flat,sprint,1,4,0.0,,,,True,4.4874110562665575,1.2522033525119995,0.2874110562665573, +linear-ascend-sprint-mm1-gap4-dy1p0,linear,ascend,sprint,1,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap4-dym1p0,linear,descend,sprint,1,4,-1.0,,,,True,5.045518628294644,1.2522033525119995,0.8455186282946441, +linear-descend-sprint-mm1-gap4-dym2p0,linear,descend,sprint,1,4,-2.0,,,,True,5.479649670920165,1.2522033525119995,1.2796496709201648, +linear-flat-sprint-mm1-gap5-dy0p0,linear,flat,sprint,1,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap5-dy1p0,linear,ascend,sprint,1,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap5-dym1p0,linear,descend,sprint,1,5,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap5-dym2p0,linear,descend,sprint,1,5,-2.0,,,,True,5.479649670920165,1.2522033525119995,0.2796496709201648, +linear-flat-sprint-mm1-gap6-dy0p0,linear,flat,sprint,1,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap6-dy1p0,linear,ascend,sprint,1,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap6-dym1p0,linear,descend,sprint,1,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap6-dym2p0,linear,descend,sprint,1,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm1-gap7-dy0p0,linear,flat,sprint,1,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm1-gap7-dy1p0,linear,ascend,sprint,1,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap7-dym1p0,linear,descend,sprint,1,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm1-gap7-dym2p0,linear,descend,sprint,1,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap0-dy0p0,linear,flat,sprint,2,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap0-dy1p0,linear,ascend,sprint,2,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap0-dym1p0,linear,descend,sprint,2,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap0-dym2p0,linear,descend,sprint,2,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap1-dy0p0,linear,flat,sprint,2,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap1-dy1p0,linear,ascend,sprint,2,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap1-dym1p0,linear,descend,sprint,2,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap1-dym2p0,linear,descend,sprint,2,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap2-dy0p0,linear,flat,sprint,2,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap2-dy1p0,linear,ascend,sprint,2,2,1.0,,,,True,3.783758490667913,1.2522033525119995,1.583758490667913, +linear-descend-sprint-mm2-gap2-dym1p0,linear,descend,sprint,2,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap2-dym2p0,linear,descend,sprint,2,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap3-dy0p0,linear,flat,sprint,2,3,0.0,,,,True,4.769275490524089,1.2522033525119995,1.569275490524089, +linear-ascend-sprint-mm2-gap3-dy1p0,linear,ascend,sprint,2,3,1.0,,,,True,3.783758490667913,1.2522033525119995,0.583758490667913, +linear-descend-sprint-mm2-gap3-dym1p0,linear,descend,sprint,2,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap3-dym2p0,linear,descend,sprint,2,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap4-dy0p0,linear,flat,sprint,2,4,0.0,,,,True,4.769275490524089,1.2522033525119995,0.5692754905240891, +linear-ascend-sprint-mm2-gap4-dy1p0,linear,ascend,sprint,2,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap4-dym1p0,linear,descend,sprint,2,4,-1.0,,,,True,5.353044629088269,1.2522033525119995,1.153044629088269, +linear-descend-sprint-mm2-gap4-dym2p0,linear,descend,sprint,2,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap5-dy0p0,linear,flat,sprint,2,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap5-dy1p0,linear,ascend,sprint,2,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap5-dym1p0,linear,descend,sprint,2,5,-1.0,,,,True,5.353044629088269,1.2522033525119995,0.153044629088269, +linear-descend-sprint-mm2-gap5-dym2p0,linear,descend,sprint,2,5,-2.0,,,,True,5.804630212112152,1.2522033525119995,0.6046302121121521, +linear-flat-sprint-mm2-gap6-dy0p0,linear,flat,sprint,2,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap6-dy1p0,linear,ascend,sprint,2,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap6-dym1p0,linear,descend,sprint,2,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap6-dym2p0,linear,descend,sprint,2,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm2-gap7-dy0p0,linear,flat,sprint,2,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm2-gap7-dy1p0,linear,ascend,sprint,2,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap7-dym1p0,linear,descend,sprint,2,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm2-gap7-dym2p0,linear,descend,sprint,2,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap0-dy0p0,linear,flat,sprint,3,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap0-dy1p0,linear,ascend,sprint,3,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap0-dym1p0,linear,descend,sprint,3,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap0-dym2p0,linear,descend,sprint,3,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap1-dy0p0,linear,flat,sprint,3,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap1-dy1p0,linear,ascend,sprint,3,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap1-dym1p0,linear,descend,sprint,3,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap1-dym2p0,linear,descend,sprint,3,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap2-dy0p0,linear,flat,sprint,3,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap2-dy1p0,linear,ascend,sprint,3,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap2-dym1p0,linear,descend,sprint,3,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap2-dym2p0,linear,descend,sprint,3,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap3-dy0p0,linear,flat,sprint,3,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap3-dy1p0,linear,ascend,sprint,3,3,1.0,,,,True,3.909422904248892,1.2522033525119995,0.7094229042488918, +linear-descend-sprint-mm3-gap3-dym1p0,linear,descend,sprint,3,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap3-dym2p0,linear,descend,sprint,3,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap4-dy0p0,linear,flat,sprint,3,4,0.0,,,,True,4.9231734716287,1.2522033525119995,0.7231734716286997, +linear-ascend-sprint-mm3-gap4-dy1p0,linear,ascend,sprint,3,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap4-dym1p0,linear,descend,sprint,3,4,-1.0,,,,True,5.520953825521586,1.2522033525119995,1.3209538255215856, +linear-descend-sprint-mm3-gap4-dym2p0,linear,descend,sprint,3,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap5-dy0p0,linear,flat,sprint,3,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap5-dy1p0,linear,ascend,sprint,3,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap5-dym1p0,linear,descend,sprint,3,5,-1.0,,,,True,5.520953825521586,1.2522033525119995,0.3209538255215856, +linear-descend-sprint-mm3-gap5-dym2p0,linear,descend,sprint,3,5,-2.0,,,,True,5.982069587602977,1.2522033525119995,0.7820695876029768, +linear-flat-sprint-mm3-gap6-dy0p0,linear,flat,sprint,3,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap6-dy1p0,linear,ascend,sprint,3,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap6-dym1p0,linear,descend,sprint,3,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap6-dym2p0,linear,descend,sprint,3,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm3-gap7-dy0p0,linear,flat,sprint,3,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm3-gap7-dy1p0,linear,ascend,sprint,3,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap7-dym1p0,linear,descend,sprint,3,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm3-gap7-dym2p0,linear,descend,sprint,3,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap0-dy0p0,linear,flat,sprint,4,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap0-dy1p0,linear,ascend,sprint,4,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap0-dym1p0,linear,descend,sprint,4,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap0-dym2p0,linear,descend,sprint,4,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap1-dy0p0,linear,flat,sprint,4,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap1-dy1p0,linear,ascend,sprint,4,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap1-dym1p0,linear,descend,sprint,4,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap1-dym2p0,linear,descend,sprint,4,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap2-dy0p0,linear,flat,sprint,4,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap2-dy1p0,linear,ascend,sprint,4,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap2-dym1p0,linear,descend,sprint,4,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap2-dym2p0,linear,descend,sprint,4,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap3-dy0p0,linear,flat,sprint,4,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap3-dy1p0,linear,ascend,sprint,4,3,1.0,,,,True,3.978035674064107,1.2522033525119995,0.7780356740641068, +linear-descend-sprint-mm4-gap3-dym1p0,linear,descend,sprint,4,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap3-dym2p0,linear,descend,sprint,4,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap4-dy0p0,linear,flat,sprint,4,4,0.0,,,,True,5.007201769311817,1.2522033525119995,0.807201769311817, +linear-ascend-sprint-mm4-gap4-dy1p0,linear,ascend,sprint,4,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap4-dym1p0,linear,descend,sprint,4,4,-1.0,,,,True,5.612632246774177,1.2522033525119995,1.4126322467741765, +linear-descend-sprint-mm4-gap4-dym2p0,linear,descend,sprint,4,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap5-dy0p0,linear,flat,sprint,4,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap5-dy1p0,linear,ascend,sprint,4,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap5-dym1p0,linear,descend,sprint,4,5,-1.0,,,,True,5.612632246774177,1.2522033525119995,0.41263224677417654, +linear-descend-sprint-mm4-gap5-dym2p0,linear,descend,sprint,4,5,-2.0,,,,True,6.078951486620966,1.2522033525119995,0.8789514866209656, +linear-flat-sprint-mm4-gap6-dy0p0,linear,flat,sprint,4,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap6-dy1p0,linear,ascend,sprint,4,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap6-dym1p0,linear,descend,sprint,4,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap6-dym2p0,linear,descend,sprint,4,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm4-gap7-dy0p0,linear,flat,sprint,4,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm4-gap7-dy1p0,linear,ascend,sprint,4,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap7-dym1p0,linear,descend,sprint,4,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm4-gap7-dym2p0,linear,descend,sprint,4,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap0-dy0p0,linear,flat,sprint,5,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap0-dy1p0,linear,ascend,sprint,5,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap0-dym1p0,linear,descend,sprint,5,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap0-dym2p0,linear,descend,sprint,5,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap1-dy0p0,linear,flat,sprint,5,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap1-dy1p0,linear,ascend,sprint,5,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap1-dym1p0,linear,descend,sprint,5,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap1-dym2p0,linear,descend,sprint,5,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap2-dy0p0,linear,flat,sprint,5,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap2-dy1p0,linear,ascend,sprint,5,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap2-dym1p0,linear,descend,sprint,5,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap2-dym2p0,linear,descend,sprint,5,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap3-dy0p0,linear,flat,sprint,5,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap3-dy1p0,linear,ascend,sprint,5,3,1.0,,,,True,4.015498246383214,1.2522033525119995,0.8154982463832141, +linear-descend-sprint-mm5-gap3-dym1p0,linear,descend,sprint,5,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap3-dym2p0,linear,descend,sprint,5,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap4-dy0p0,linear,flat,sprint,5,4,0.0,,,,True,5.0530812198468,1.2522033525119995,0.8530812198467999, +linear-ascend-sprint-mm5-gap4-dy1p0,linear,ascend,sprint,5,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap4-dym1p0,linear,descend,sprint,5,4,-1.0,,,,True,5.662688664778092,1.2522033525119995,1.4626886647780921, +linear-descend-sprint-mm5-gap4-dym2p0,linear,descend,sprint,5,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap5-dy0p0,linear,flat,sprint,5,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap5-dy1p0,linear,ascend,sprint,5,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap5-dym1p0,linear,descend,sprint,5,5,-1.0,,,,True,5.662688664778092,1.2522033525119995,0.4626886647780921, +linear-descend-sprint-mm5-gap5-dym2p0,linear,descend,sprint,5,5,-2.0,,,,True,6.131849003484789,1.2522033525119995,0.931849003484789, +linear-flat-sprint-mm5-gap6-dy0p0,linear,flat,sprint,5,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap6-dy1p0,linear,ascend,sprint,5,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap6-dym1p0,linear,descend,sprint,5,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap6-dym2p0,linear,descend,sprint,5,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm5-gap7-dy0p0,linear,flat,sprint,5,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm5-gap7-dy1p0,linear,ascend,sprint,5,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap7-dym1p0,linear,descend,sprint,5,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm5-gap7-dym2p0,linear,descend,sprint,5,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap0-dy0p0,linear,flat,sprint,6,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap0-dy1p0,linear,ascend,sprint,6,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap0-dym1p0,linear,descend,sprint,6,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap0-dym2p0,linear,descend,sprint,6,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap1-dy0p0,linear,flat,sprint,6,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap1-dy1p0,linear,ascend,sprint,6,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap1-dym1p0,linear,descend,sprint,6,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap1-dym2p0,linear,descend,sprint,6,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap2-dy0p0,linear,flat,sprint,6,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap2-dy1p0,linear,ascend,sprint,6,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap2-dym1p0,linear,descend,sprint,6,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap2-dym2p0,linear,descend,sprint,6,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap3-dy0p0,linear,flat,sprint,6,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap3-dy1p0,linear,ascend,sprint,6,3,1.0,,,,True,4.035952810869447,1.2522033525119995,0.835952810869447, +linear-descend-sprint-mm6-gap3-dym1p0,linear,descend,sprint,6,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap3-dym2p0,linear,descend,sprint,6,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap4-dy0p0,linear,flat,sprint,6,4,0.0,,,,True,5.078131399838901,1.2522033525119995,0.8781313998389004, +linear-ascend-sprint-mm6-gap4-dy1p0,linear,ascend,sprint,6,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap4-dym1p0,linear,descend,sprint,6,4,-1.0,,,,True,5.69001946900823,1.2522033525119995,1.4900194690082298, +linear-descend-sprint-mm6-gap4-dym2p0,linear,descend,sprint,6,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap5-dy0p0,linear,flat,sprint,6,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap5-dy1p0,linear,ascend,sprint,6,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap5-dym1p0,linear,descend,sprint,6,5,-1.0,,,,True,5.69001946900823,1.2522033525119995,0.4900194690082298, +linear-descend-sprint-mm6-gap5-dym2p0,linear,descend,sprint,6,5,-2.0,,,,True,6.160731047692436,1.2522033525119995,0.9607310476924358, +linear-flat-sprint-mm6-gap6-dy0p0,linear,flat,sprint,6,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap6-dy1p0,linear,ascend,sprint,6,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap6-dym1p0,linear,descend,sprint,6,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap6-dym2p0,linear,descend,sprint,6,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm6-gap7-dy0p0,linear,flat,sprint,6,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm6-gap7-dy1p0,linear,ascend,sprint,6,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap7-dym1p0,linear,descend,sprint,6,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm6-gap7-dym2p0,linear,descend,sprint,6,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap0-dy0p0,linear,flat,sprint,7,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap0-dy1p0,linear,ascend,sprint,7,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap0-dym1p0,linear,descend,sprint,7,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap0-dym2p0,linear,descend,sprint,7,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap1-dy0p0,linear,flat,sprint,7,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap1-dy1p0,linear,ascend,sprint,7,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap1-dym1p0,linear,descend,sprint,7,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap1-dym2p0,linear,descend,sprint,7,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap2-dy0p0,linear,flat,sprint,7,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap2-dy1p0,linear,ascend,sprint,7,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap2-dym1p0,linear,descend,sprint,7,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap2-dym2p0,linear,descend,sprint,7,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap3-dy0p0,linear,flat,sprint,7,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap3-dy1p0,linear,ascend,sprint,7,3,1.0,,,,True,4.04712100307893,1.2522033525119995,0.8471210030789296, +linear-descend-sprint-mm7-gap3-dym1p0,linear,descend,sprint,7,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap3-dym2p0,linear,descend,sprint,7,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap4-dy0p0,linear,flat,sprint,7,4,0.0,,,,True,5.091808798114586,1.2522033525119995,0.8918087981145861, +linear-ascend-sprint-mm7-gap4-dy1p0,linear,ascend,sprint,7,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap4-dym1p0,linear,descend,sprint,7,4,-1.0,,,,True,5.704942088117885,1.2522033525119995,1.5049420881178852, +linear-descend-sprint-mm7-gap4-dym2p0,linear,descend,sprint,7,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap5-dy0p0,linear,flat,sprint,7,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap5-dy1p0,linear,ascend,sprint,7,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap5-dym1p0,linear,descend,sprint,7,5,-1.0,,,,True,5.704942088117885,1.2522033525119995,0.5049420881178852, +linear-descend-sprint-mm7-gap5-dym2p0,linear,descend,sprint,7,5,-2.0,,,,True,6.176500643829812,1.2522033525119995,0.9765006438298114, +linear-flat-sprint-mm7-gap6-dy0p0,linear,flat,sprint,7,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap6-dy1p0,linear,ascend,sprint,7,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap6-dym1p0,linear,descend,sprint,7,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap6-dym2p0,linear,descend,sprint,7,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm7-gap7-dy0p0,linear,flat,sprint,7,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm7-gap7-dy1p0,linear,ascend,sprint,7,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap7-dym1p0,linear,descend,sprint,7,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm7-gap7-dym2p0,linear,descend,sprint,7,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap0-dy0p0,linear,flat,sprint,8,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap0-dy1p0,linear,ascend,sprint,8,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap0-dym1p0,linear,descend,sprint,8,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap0-dym2p0,linear,descend,sprint,8,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap1-dy0p0,linear,flat,sprint,8,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap1-dy1p0,linear,ascend,sprint,8,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap1-dym1p0,linear,descend,sprint,8,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap1-dym2p0,linear,descend,sprint,8,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap2-dy0p0,linear,flat,sprint,8,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap2-dy1p0,linear,ascend,sprint,8,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap2-dym1p0,linear,descend,sprint,8,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap2-dym2p0,linear,descend,sprint,8,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap3-dy0p0,linear,flat,sprint,8,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap3-dy1p0,linear,ascend,sprint,8,3,1.0,,,,True,4.053218836025307,1.2522033525119995,0.8532188360253068, +linear-descend-sprint-mm8-gap3-dym1p0,linear,descend,sprint,8,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap3-dym2p0,linear,descend,sprint,8,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap4-dy0p0,linear,flat,sprint,8,4,0.0,,,,True,5.099276657573112,1.2522033525119995,0.8992766575731119, +linear-ascend-sprint-mm8-gap4-dy1p0,linear,ascend,sprint,8,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap4-dym1p0,linear,descend,sprint,8,4,-1.0,,,,True,5.713089838151757,1.2522033525119995,1.5130898381517568, +linear-descend-sprint-mm8-gap4-dym2p0,linear,descend,sprint,8,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap5-dy0p0,linear,flat,sprint,8,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap5-dy1p0,linear,ascend,sprint,8,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap5-dym1p0,linear,descend,sprint,8,5,-1.0,,,,True,5.713089838151757,1.2522033525119995,0.5130898381517568, +linear-descend-sprint-mm8-gap5-dym2p0,linear,descend,sprint,8,5,-2.0,,,,True,6.185110843320819,1.2522033525119995,0.9851108433208191, +linear-flat-sprint-mm8-gap6-dy0p0,linear,flat,sprint,8,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap6-dy1p0,linear,ascend,sprint,8,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap6-dym1p0,linear,descend,sprint,8,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap6-dym2p0,linear,descend,sprint,8,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm8-gap7-dy0p0,linear,flat,sprint,8,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm8-gap7-dy1p0,linear,ascend,sprint,8,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap7-dym1p0,linear,descend,sprint,8,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm8-gap7-dym2p0,linear,descend,sprint,8,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap0-dy0p0,linear,flat,sprint,9,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap0-dy1p0,linear,ascend,sprint,9,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap0-dym1p0,linear,descend,sprint,9,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap0-dym2p0,linear,descend,sprint,9,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap1-dy0p0,linear,flat,sprint,9,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap1-dy1p0,linear,ascend,sprint,9,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap1-dym1p0,linear,descend,sprint,9,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap1-dym2p0,linear,descend,sprint,9,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap2-dy0p0,linear,flat,sprint,9,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap2-dy1p0,linear,ascend,sprint,9,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap2-dym1p0,linear,descend,sprint,9,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap2-dym2p0,linear,descend,sprint,9,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap3-dy0p0,linear,flat,sprint,9,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap3-dy1p0,linear,ascend,sprint,9,3,1.0,,,,True,4.0565482528140295,1.2522033525119995,0.8565482528140294, +linear-descend-sprint-mm9-gap3-dym1p0,linear,descend,sprint,9,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap3-dym2p0,linear,descend,sprint,9,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap4-dy0p0,linear,flat,sprint,9,4,0.0,,,,True,5.103354108837465,1.2522033525119995,0.9033541088374646, +linear-ascend-sprint-mm9-gap4-dy1p0,linear,ascend,sprint,9,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap4-dym1p0,linear,descend,sprint,9,4,-1.0,,,,True,5.717538509670249,1.2522033525119995,1.5175385096702492, +linear-descend-sprint-mm9-gap4-dym2p0,linear,descend,sprint,9,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap5-dy0p0,linear,flat,sprint,9,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap5-dy1p0,linear,ascend,sprint,9,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap5-dym1p0,linear,descend,sprint,9,5,-1.0,,,,True,5.717538509670249,1.2522033525119995,0.5175385096702492, +linear-descend-sprint-mm9-gap5-dym2p0,linear,descend,sprint,9,5,-2.0,,,,True,6.189812012242907,1.2522033525119995,0.9898120122429068, +linear-flat-sprint-mm9-gap6-dy0p0,linear,flat,sprint,9,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap6-dy1p0,linear,ascend,sprint,9,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap6-dym1p0,linear,descend,sprint,9,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap6-dym2p0,linear,descend,sprint,9,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm9-gap7-dy0p0,linear,flat,sprint,9,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm9-gap7-dy1p0,linear,ascend,sprint,9,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap7-dym1p0,linear,descend,sprint,9,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm9-gap7-dym2p0,linear,descend,sprint,9,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap0-dy0p0,linear,flat,sprint,10,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap0-dy1p0,linear,ascend,sprint,10,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap0-dym1p0,linear,descend,sprint,10,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap0-dym2p0,linear,descend,sprint,10,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap1-dy0p0,linear,flat,sprint,10,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap1-dy1p0,linear,ascend,sprint,10,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap1-dym1p0,linear,descend,sprint,10,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap1-dym2p0,linear,descend,sprint,10,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap2-dy0p0,linear,flat,sprint,10,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap2-dy1p0,linear,ascend,sprint,10,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap2-dym1p0,linear,descend,sprint,10,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap2-dym2p0,linear,descend,sprint,10,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap3-dy0p0,linear,flat,sprint,10,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap3-dy1p0,linear,ascend,sprint,10,3,1.0,,,,True,4.0583661143806715,1.2522033525119995,0.8583661143806713, +linear-descend-sprint-mm10-gap3-dym1p0,linear,descend,sprint,10,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap3-dym2p0,linear,descend,sprint,10,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap4-dy0p0,linear,flat,sprint,10,4,0.0,,,,True,5.105580397227805,1.2522033525119995,0.9055803972278049, +linear-ascend-sprint-mm10-gap4-dy1p0,linear,ascend,sprint,10,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap4-dym1p0,linear,descend,sprint,10,4,-1.0,,,,True,5.719967484319349,1.2522033525119995,1.5199674843193485, +linear-descend-sprint-mm10-gap4-dym2p0,linear,descend,sprint,10,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap5-dy0p0,linear,flat,sprint,10,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap5-dy1p0,linear,ascend,sprint,10,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap5-dym1p0,linear,descend,sprint,10,5,-1.0,,,,True,5.719967484319349,1.2522033525119995,0.5199674843193485, +linear-descend-sprint-mm10-gap5-dym2p0,linear,descend,sprint,10,5,-2.0,,,,True,6.192378850474369,1.2522033525119995,0.9923788504743687, +linear-flat-sprint-mm10-gap6-dy0p0,linear,flat,sprint,10,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap6-dy1p0,linear,ascend,sprint,10,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap6-dym1p0,linear,descend,sprint,10,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap6-dym2p0,linear,descend,sprint,10,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm10-gap7-dy0p0,linear,flat,sprint,10,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm10-gap7-dy1p0,linear,ascend,sprint,10,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap7-dym1p0,linear,descend,sprint,10,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm10-gap7-dym2p0,linear,descend,sprint,10,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap0-dy0p0,linear,flat,sprint,11,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap0-dy1p0,linear,ascend,sprint,11,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap0-dym1p0,linear,descend,sprint,11,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap0-dym2p0,linear,descend,sprint,11,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap1-dy0p0,linear,flat,sprint,11,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap1-dy1p0,linear,ascend,sprint,11,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap1-dym1p0,linear,descend,sprint,11,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap1-dym2p0,linear,descend,sprint,11,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap2-dy0p0,linear,flat,sprint,11,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap2-dy1p0,linear,ascend,sprint,11,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap2-dym1p0,linear,descend,sprint,11,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap2-dym2p0,linear,descend,sprint,11,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap3-dy0p0,linear,flat,sprint,11,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap3-dy1p0,linear,ascend,sprint,11,3,1.0,,,,True,4.059358666796059,1.2522033525119995,0.8593586667960587, +linear-descend-sprint-mm11-gap3-dym1p0,linear,descend,sprint,11,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap3-dym2p0,linear,descend,sprint,11,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap4-dy0p0,linear,flat,sprint,11,4,0.0,,,,True,5.106795950688928,1.2522033525119995,0.9067959506889283, +linear-ascend-sprint-mm11-gap4-dy1p0,linear,ascend,sprint,11,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap4-dym1p0,linear,descend,sprint,11,4,-1.0,,,,True,5.721293704477756,1.2522033525119995,1.5212937044777561, +linear-descend-sprint-mm11-gap4-dym2p0,linear,descend,sprint,11,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap5-dy0p0,linear,flat,sprint,11,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap5-dy1p0,linear,ascend,sprint,11,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap5-dym1p0,linear,descend,sprint,11,5,-1.0,,,,True,5.721293704477756,1.2522033525119995,0.5212937044777561, +linear-descend-sprint-mm11-gap5-dym2p0,linear,descend,sprint,11,5,-2.0,,,,True,6.193780344148747,1.2522033525119995,0.9937803441487469, +linear-flat-sprint-mm11-gap6-dy0p0,linear,flat,sprint,11,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap6-dy1p0,linear,ascend,sprint,11,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap6-dym1p0,linear,descend,sprint,11,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap6-dym2p0,linear,descend,sprint,11,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm11-gap7-dy0p0,linear,flat,sprint,11,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm11-gap7-dy1p0,linear,ascend,sprint,11,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap7-dym1p0,linear,descend,sprint,11,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm11-gap7-dym2p0,linear,descend,sprint,11,7,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap0-dy0p0,linear,flat,sprint,12,0,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap0-dy1p0,linear,ascend,sprint,12,0,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap0-dym1p0,linear,descend,sprint,12,0,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap0-dym2p0,linear,descend,sprint,12,0,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap1-dy0p0,linear,flat,sprint,12,1,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap1-dy1p0,linear,ascend,sprint,12,1,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap1-dym1p0,linear,descend,sprint,12,1,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap1-dym2p0,linear,descend,sprint,12,1,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap2-dy0p0,linear,flat,sprint,12,2,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap2-dy1p0,linear,ascend,sprint,12,2,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap2-dym1p0,linear,descend,sprint,12,2,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap2-dym2p0,linear,descend,sprint,12,2,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap3-dy0p0,linear,flat,sprint,12,3,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap3-dy1p0,linear,ascend,sprint,12,3,1.0,,,,True,4.05990060041486,1.2522033525119995,0.8599006004148597, +linear-descend-sprint-mm12-gap3-dym1p0,linear,descend,sprint,12,3,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap3-dym2p0,linear,descend,sprint,12,3,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap4-dy0p0,linear,flat,sprint,12,4,0.0,,,,True,5.107459642878702,1.2522033525119995,0.9074596428787016, +linear-ascend-sprint-mm12-gap4-dy1p0,linear,ascend,sprint,12,4,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap4-dym1p0,linear,descend,sprint,12,4,-1.0,,,,True,5.722017820684245,1.2522033525119995,1.522017820684245, +linear-descend-sprint-mm12-gap4-dym2p0,linear,descend,sprint,12,4,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap5-dy0p0,linear,flat,sprint,12,5,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap5-dy1p0,linear,ascend,sprint,12,5,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap5-dym1p0,linear,descend,sprint,12,5,-1.0,,,,True,5.722017820684245,1.2522033525119995,0.522017820684245, +linear-descend-sprint-mm12-gap5-dym2p0,linear,descend,sprint,12,5,-2.0,,,,True,6.194545559694956,1.2522033525119995,0.9945455596949557, +linear-flat-sprint-mm12-gap6-dy0p0,linear,flat,sprint,12,6,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap6-dy1p0,linear,ascend,sprint,12,6,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap6-dym1p0,linear,descend,sprint,12,6,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap6-dym2p0,linear,descend,sprint,12,6,-2.0,,,,False,,1.2522033525119995,, +linear-flat-sprint-mm12-gap7-dy0p0,linear,flat,sprint,12,7,0.0,,,,False,,1.2522033525119995,, +linear-ascend-sprint-mm12-gap7-dy1p0,linear,ascend,sprint,12,7,1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap7-dym1p0,linear,descend,sprint,12,7,-1.0,,,,False,,1.2522033525119995,, +linear-descend-sprint-mm12-gap7-dym2p0,linear,descend,sprint,12,7,-2.0,,,,False,,1.2522033525119995,, +neo-neo-walk-mm0-wall1,neo,neo,walk,0,,0.0,,1,,True,2.517113405646343,1.2522033525119995,0.9171134056463428, +neo-neo-walk-mm0-wall2,neo,neo,walk,0,,0.0,,2,,False,2.517113405646343,1.2522033525119995,-0.0828865943536572, +neo-neo-walk-mm0-wall3,neo,neo,walk,0,,0.0,,3,,False,2.517113405646343,1.2522033525119995,-1.0828865943536572, +neo-neo-walk-mm0-wall4,neo,neo,walk,0,,0.0,,4,,False,2.517113405646343,1.2522033525119995,-2.0828865943536568, +neo-neo-walk-mm1-wall1,neo,neo,walk,1,,0.0,,1,,True,3.0333486332242363,1.2522033525119995,1.4333486332242362, +neo-neo-walk-mm1-wall2,neo,neo,walk,1,,0.0,,2,,True,3.0333486332242363,1.2522033525119995,0.4333486332242362, +neo-neo-walk-mm1-wall3,neo,neo,walk,1,,0.0,,3,,False,3.0333486332242363,1.2522033525119995,-0.5666513667757638, +neo-neo-walk-mm1-wall4,neo,neo,walk,1,,0.0,,4,,False,3.0333486332242363,1.2522033525119995,-1.5666513667757633, +neo-neo-walk-mm2-wall1,neo,neo,walk,2,,0.0,,1,,True,3.315213067481766,1.2522033525119995,1.7152130674817658, +neo-neo-walk-mm2-wall2,neo,neo,walk,2,,0.0,,2,,True,3.315213067481766,1.2522033525119995,0.7152130674817658, +neo-neo-walk-mm2-wall3,neo,neo,walk,2,,0.0,,3,,False,3.315213067481766,1.2522033525119995,-0.28478693251823417, +neo-neo-walk-mm2-wall4,neo,neo,walk,2,,0.0,,4,,False,3.315213067481766,1.2522033525119995,-1.2847869325182337, +neo-neo-walk-mm3-wall1,neo,neo,walk,3,,0.0,,1,,True,3.469111048586376,1.2522033525119995,1.869111048586376, +neo-neo-walk-mm3-wall2,neo,neo,walk,3,,0.0,,2,,True,3.469111048586376,1.2522033525119995,0.869111048586376, +neo-neo-walk-mm3-wall3,neo,neo,walk,3,,0.0,,3,,False,3.469111048586376,1.2522033525119995,-0.130888951413624, +neo-neo-walk-mm3-wall4,neo,neo,walk,3,,0.0,,4,,False,3.469111048586376,1.2522033525119995,-1.1308889514136236, +neo-neo-walk-mm4-wall1,neo,neo,walk,4,,0.0,,1,,True,3.5531393462694942,1.2522033525119995,1.9531393462694941, +neo-neo-walk-mm4-wall2,neo,neo,walk,4,,0.0,,2,,True,3.5531393462694942,1.2522033525119995,0.9531393462694941, +neo-neo-walk-mm4-wall3,neo,neo,walk,4,,0.0,,3,,False,3.5531393462694942,1.2522033525119995,-0.04686065373050585, +neo-neo-walk-mm4-wall4,neo,neo,walk,4,,0.0,,4,,False,3.5531393462694942,1.2522033525119995,-1.0468606537305054, +neo-neo-walk-mm5-wall1,neo,neo,walk,5,,0.0,,1,,True,3.599018796804477,1.2522033525119995,1.999018796804477, +neo-neo-walk-mm5-wall2,neo,neo,walk,5,,0.0,,2,,True,3.599018796804477,1.2522033525119995,0.999018796804477, +neo-neo-walk-mm5-wall3,neo,neo,walk,5,,0.0,,3,,False,3.599018796804477,1.2522033525119995,-0.0009812031955229727, +neo-neo-walk-mm5-wall4,neo,neo,walk,5,,0.0,,4,,False,3.599018796804477,1.2522033525119995,-1.0009812031955225, +neo-neo-walk-mm6-wall1,neo,neo,walk,6,,0.0,,1,,True,3.624068976796577,1.2522033525119995,2.0240689767965767, +neo-neo-walk-mm6-wall2,neo,neo,walk,6,,0.0,,2,,True,3.624068976796577,1.2522033525119995,1.0240689767965767, +neo-neo-walk-mm6-wall3,neo,neo,walk,6,,0.0,,3,,True,3.624068976796577,1.2522033525119995,0.024068976796576713, +neo-neo-walk-mm6-wall4,neo,neo,walk,6,,0.0,,4,,False,3.624068976796577,1.2522033525119995,-0.9759310232034228, +neo-neo-walk-mm7-wall1,neo,neo,walk,7,,0.0,,1,,True,3.6377463750722634,1.2522033525119995,2.0377463750722633, +neo-neo-walk-mm7-wall2,neo,neo,walk,7,,0.0,,2,,True,3.6377463750722634,1.2522033525119995,1.0377463750722633, +neo-neo-walk-mm7-wall3,neo,neo,walk,7,,0.0,,3,,True,3.6377463750722634,1.2522033525119995,0.037746375072263305, +neo-neo-walk-mm7-wall4,neo,neo,walk,7,,0.0,,4,,False,3.6377463750722634,1.2522033525119995,-0.9622536249277363, +neo-neo-walk-mm8-wall1,neo,neo,walk,8,,0.0,,1,,True,3.6452142345307887,1.2522033525119995,2.0452142345307887, +neo-neo-walk-mm8-wall2,neo,neo,walk,8,,0.0,,2,,True,3.6452142345307887,1.2522033525119995,1.0452142345307887, +neo-neo-walk-mm8-wall3,neo,neo,walk,8,,0.0,,3,,True,3.6452142345307887,1.2522033525119995,0.045214234530788655, +neo-neo-walk-mm8-wall4,neo,neo,walk,8,,0.0,,4,,False,3.6452142345307887,1.2522033525119995,-0.9547857654692109, +neo-neo-walk-mm9-wall1,neo,neo,walk,9,,0.0,,1,,True,3.6492916857951427,1.2522033525119995,2.0492916857951426, +neo-neo-walk-mm9-wall2,neo,neo,walk,9,,0.0,,2,,True,3.6492916857951427,1.2522033525119995,1.0492916857951426, +neo-neo-walk-mm9-wall3,neo,neo,walk,9,,0.0,,3,,True,3.6492916857951427,1.2522033525119995,0.04929168579514265, +neo-neo-walk-mm9-wall4,neo,neo,walk,9,,0.0,,4,,False,3.6492916857951427,1.2522033525119995,-0.9507083142048569, +neo-neo-walk-mm10-wall1,neo,neo,walk,10,,0.0,,1,,True,3.651517974185481,1.2522033525119995,2.0515179741854808, +neo-neo-walk-mm10-wall2,neo,neo,walk,10,,0.0,,2,,True,3.651517974185481,1.2522033525119995,1.0515179741854808, +neo-neo-walk-mm10-wall3,neo,neo,walk,10,,0.0,,3,,True,3.651517974185481,1.2522033525119995,0.051517974185480764, +neo-neo-walk-mm10-wall4,neo,neo,walk,10,,0.0,,4,,False,3.651517974185481,1.2522033525119995,-0.9484820258145188, +neo-neo-walk-mm11-wall1,neo,neo,walk,11,,0.0,,1,,True,3.652733527646605,1.2522033525119995,2.052733527646605, +neo-neo-walk-mm11-wall2,neo,neo,walk,11,,0.0,,2,,True,3.652733527646605,1.2522033525119995,1.052733527646605, +neo-neo-walk-mm11-wall3,neo,neo,walk,11,,0.0,,3,,True,3.652733527646605,1.2522033525119995,0.052733527646604994, +neo-neo-walk-mm11-wall4,neo,neo,walk,11,,0.0,,4,,False,3.652733527646605,1.2522033525119995,-0.9472664723533946, +neo-neo-walk-mm12-wall1,neo,neo,walk,12,,0.0,,1,,True,3.6533972198363784,1.2522033525119995,2.0533972198363784, +neo-neo-walk-mm12-wall2,neo,neo,walk,12,,0.0,,2,,True,3.6533972198363784,1.2522033525119995,1.0533972198363784, +neo-neo-walk-mm12-wall3,neo,neo,walk,12,,0.0,,3,,True,3.6533972198363784,1.2522033525119995,0.05339721983637835, +neo-neo-walk-mm12-wall4,neo,neo,walk,12,,0.0,,4,,False,3.6533972198363784,1.2522033525119995,-0.9466027801636212, +neo-neo-sprint-mm0-wall1,neo,neo,sprint,0,,0.0,,1,,True,3.971175828688666,1.2522033525119995,2.3711758286886657, +neo-neo-sprint-mm0-wall2,neo,neo,sprint,0,,0.0,,2,,True,3.971175828688666,1.2522033525119995,1.3711758286886657, +neo-neo-sprint-mm0-wall3,neo,neo,sprint,0,,0.0,,3,,True,3.971175828688666,1.2522033525119995,0.3711758286886657, +neo-neo-sprint-mm0-wall4,neo,neo,sprint,0,,0.0,,4,,False,3.971175828688666,1.2522033525119995,-0.6288241713113338, +neo-neo-sprint-mm1-wall1,neo,neo,sprint,1,,0.0,,1,,True,4.4874110562665575,1.2522033525119995,2.8874110562665574, +neo-neo-sprint-mm1-wall2,neo,neo,sprint,1,,0.0,,2,,True,4.4874110562665575,1.2522033525119995,1.8874110562665574, +neo-neo-sprint-mm1-wall3,neo,neo,sprint,1,,0.0,,3,,True,4.4874110562665575,1.2522033525119995,0.8874110562665574, +neo-neo-sprint-mm1-wall4,neo,neo,sprint,1,,0.0,,4,,False,4.4874110562665575,1.2522033525119995,-0.11258894373344219, +neo-neo-sprint-mm2-wall1,neo,neo,sprint,2,,0.0,,1,,True,4.769275490524089,1.2522033525119995,3.169275490524089, +neo-neo-sprint-mm2-wall2,neo,neo,sprint,2,,0.0,,2,,True,4.769275490524089,1.2522033525119995,2.169275490524089, +neo-neo-sprint-mm2-wall3,neo,neo,sprint,2,,0.0,,3,,True,4.769275490524089,1.2522033525119995,1.1692754905240892, +neo-neo-sprint-mm2-wall4,neo,neo,sprint,2,,0.0,,4,,True,4.769275490524089,1.2522033525119995,0.16927549052408963, +neo-neo-sprint-mm3-wall1,neo,neo,sprint,3,,0.0,,1,,True,4.9231734716287,1.2522033525119995,3.3231734716287, +neo-neo-sprint-mm3-wall2,neo,neo,sprint,3,,0.0,,2,,True,4.9231734716287,1.2522033525119995,2.3231734716287, +neo-neo-sprint-mm3-wall3,neo,neo,sprint,3,,0.0,,3,,True,4.9231734716287,1.2522033525119995,1.3231734716286998, +neo-neo-sprint-mm3-wall4,neo,neo,sprint,3,,0.0,,4,,True,4.9231734716287,1.2522033525119995,0.32317347162870025, +neo-neo-sprint-mm4-wall1,neo,neo,sprint,4,,0.0,,1,,True,5.007201769311817,1.2522033525119995,3.407201769311817, +neo-neo-sprint-mm4-wall2,neo,neo,sprint,4,,0.0,,2,,True,5.007201769311817,1.2522033525119995,2.407201769311817, +neo-neo-sprint-mm4-wall3,neo,neo,sprint,4,,0.0,,3,,True,5.007201769311817,1.2522033525119995,1.407201769311817, +neo-neo-sprint-mm4-wall4,neo,neo,sprint,4,,0.0,,4,,True,5.007201769311817,1.2522033525119995,0.4072017693118175, +neo-neo-sprint-mm5-wall1,neo,neo,sprint,5,,0.0,,1,,True,5.0530812198468,1.2522033525119995,3.4530812198468, +neo-neo-sprint-mm5-wall2,neo,neo,sprint,5,,0.0,,2,,True,5.0530812198468,1.2522033525119995,2.4530812198468, +neo-neo-sprint-mm5-wall3,neo,neo,sprint,5,,0.0,,3,,True,5.0530812198468,1.2522033525119995,1.4530812198468, +neo-neo-sprint-mm5-wall4,neo,neo,sprint,5,,0.0,,4,,True,5.0530812198468,1.2522033525119995,0.4530812198468004, +neo-neo-sprint-mm6-wall1,neo,neo,sprint,6,,0.0,,1,,True,5.078131399838901,1.2522033525119995,3.4781313998389005, +neo-neo-sprint-mm6-wall2,neo,neo,sprint,6,,0.0,,2,,True,5.078131399838901,1.2522033525119995,2.4781313998389005, +neo-neo-sprint-mm6-wall3,neo,neo,sprint,6,,0.0,,3,,True,5.078131399838901,1.2522033525119995,1.4781313998389005, +neo-neo-sprint-mm6-wall4,neo,neo,sprint,6,,0.0,,4,,True,5.078131399838901,1.2522033525119995,0.47813139983890096, +neo-neo-sprint-mm7-wall1,neo,neo,sprint,7,,0.0,,1,,True,5.091808798114586,1.2522033525119995,3.491808798114586, +neo-neo-sprint-mm7-wall2,neo,neo,sprint,7,,0.0,,2,,True,5.091808798114586,1.2522033525119995,2.491808798114586, +neo-neo-sprint-mm7-wall3,neo,neo,sprint,7,,0.0,,3,,True,5.091808798114586,1.2522033525119995,1.4918087981145862, +neo-neo-sprint-mm7-wall4,neo,neo,sprint,7,,0.0,,4,,True,5.091808798114586,1.2522033525119995,0.49180879811458666, +neo-neo-sprint-mm8-wall1,neo,neo,sprint,8,,0.0,,1,,True,5.099276657573112,1.2522033525119995,3.499276657573112, +neo-neo-sprint-mm8-wall2,neo,neo,sprint,8,,0.0,,2,,True,5.099276657573112,1.2522033525119995,2.499276657573112, +neo-neo-sprint-mm8-wall3,neo,neo,sprint,8,,0.0,,3,,True,5.099276657573112,1.2522033525119995,1.499276657573112, +neo-neo-sprint-mm8-wall4,neo,neo,sprint,8,,0.0,,4,,True,5.099276657573112,1.2522033525119995,0.49927665757311246, +neo-neo-sprint-mm9-wall1,neo,neo,sprint,9,,0.0,,1,,True,5.103354108837465,1.2522033525119995,3.5033541088374647, +neo-neo-sprint-mm9-wall2,neo,neo,sprint,9,,0.0,,2,,True,5.103354108837465,1.2522033525119995,2.5033541088374647, +neo-neo-sprint-mm9-wall3,neo,neo,sprint,9,,0.0,,3,,True,5.103354108837465,1.2522033525119995,1.5033541088374647, +neo-neo-sprint-mm9-wall4,neo,neo,sprint,9,,0.0,,4,,True,5.103354108837465,1.2522033525119995,0.5033541088374651, +neo-neo-sprint-mm10-wall1,neo,neo,sprint,10,,0.0,,1,,True,5.105580397227805,1.2522033525119995,3.505580397227805, +neo-neo-sprint-mm10-wall2,neo,neo,sprint,10,,0.0,,2,,True,5.105580397227805,1.2522033525119995,2.505580397227805, +neo-neo-sprint-mm10-wall3,neo,neo,sprint,10,,0.0,,3,,True,5.105580397227805,1.2522033525119995,1.505580397227805, +neo-neo-sprint-mm10-wall4,neo,neo,sprint,10,,0.0,,4,,True,5.105580397227805,1.2522033525119995,0.5055803972278055, +neo-neo-sprint-mm11-wall1,neo,neo,sprint,11,,0.0,,1,,True,5.106795950688928,1.2522033525119995,3.5067959506889284, +neo-neo-sprint-mm11-wall2,neo,neo,sprint,11,,0.0,,2,,True,5.106795950688928,1.2522033525119995,2.5067959506889284, +neo-neo-sprint-mm11-wall3,neo,neo,sprint,11,,0.0,,3,,True,5.106795950688928,1.2522033525119995,1.5067959506889284, +neo-neo-sprint-mm11-wall4,neo,neo,sprint,11,,0.0,,4,,True,5.106795950688928,1.2522033525119995,0.5067959506889288, +neo-neo-sprint-mm12-wall1,neo,neo,sprint,12,,0.0,,1,,True,5.107459642878702,1.2522033525119995,3.5074596428787017, +neo-neo-sprint-mm12-wall2,neo,neo,sprint,12,,0.0,,2,,True,5.107459642878702,1.2522033525119995,2.5074596428787017, +neo-neo-sprint-mm12-wall3,neo,neo,sprint,12,,0.0,,3,,True,5.107459642878702,1.2522033525119995,1.5074596428787017, +neo-neo-sprint-mm12-wall4,neo,neo,sprint,12,,0.0,,4,,True,5.107459642878702,1.2522033525119995,0.5074596428787022, +ceiling-headhitter-sprint-mm0-gap1-ceil4p0,ceiling,headhitter,sprint,0,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm0-gap2-ceil4p0,ceiling,headhitter,sprint,0,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm0-gap3-ceil4p0,ceiling,headhitter,sprint,0,3,0.0,4.0,,,True,3.971175828688666,1.2522033525119995,0.7711758286886656, +ceiling-headhitter-sprint-mm0-gap4-ceil4p0,ceiling,headhitter,sprint,0,4,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm0-gap1-ceil3p0,ceiling,headhitter,sprint,0,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm0-gap2-ceil3p0,ceiling,headhitter,sprint,0,2,0.0,3.0,,,True,3.671522403140402,1.2,1.4715224031404017, +ceiling-headhitter-sprint-mm0-gap3-ceil3p0,ceiling,headhitter,sprint,0,3,0.0,3.0,,,True,3.671522403140402,1.2,0.4715224031404017, +ceiling-headhitter-sprint-mm0-gap4-ceil3p0,ceiling,headhitter,sprint,0,4,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm0-gap1-ceil2p5,ceiling,headhitter,sprint,0,1,0.0,2.5,,,True,2.4809562314414415,0.7,1.2809562314414416, +ceiling-headhitter-sprint-mm0-gap2-ceil2p5,ceiling,headhitter,sprint,0,2,0.0,2.5,,,True,2.4809562314414415,0.7,0.28095623144144133, +ceiling-headhitter-sprint-mm0-gap3-ceil2p5,ceiling,headhitter,sprint,0,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm0-gap4-ceil2p5,ceiling,headhitter,sprint,0,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm0-gap1-ceil2p0,ceiling,headhitter,sprint,0,1,0.0,2.0,,,True,1.593413404024811,0.19999999999999996,0.39341340402481095, +ceiling-headhitter-sprint-mm0-gap2-ceil2p0,ceiling,headhitter,sprint,0,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm0-gap3-ceil2p0,ceiling,headhitter,sprint,0,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm0-gap4-ceil2p0,ceiling,headhitter,sprint,0,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm0-gap1-ceil1p8125,ceiling,headhitter,sprint,0,1,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm0-gap2-ceil1p8125,ceiling,headhitter,sprint,0,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm0-gap3-ceil1p8125,ceiling,headhitter,sprint,0,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm0-gap4-ceil1p8125,ceiling,headhitter,sprint,0,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm1-gap1-ceil4p0,ceiling,headhitter,sprint,1,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm1-gap2-ceil4p0,ceiling,headhitter,sprint,1,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm1-gap3-ceil4p0,ceiling,headhitter,sprint,1,3,0.0,4.0,,,True,4.4874110562665575,1.2522033525119995,1.2874110562665573, +ceiling-headhitter-sprint-mm1-gap4-ceil4p0,ceiling,headhitter,sprint,1,4,0.0,4.0,,,True,4.4874110562665575,1.2522033525119995,0.2874110562665573, +ceiling-headhitter-sprint-mm1-gap1-ceil3p0,ceiling,headhitter,sprint,1,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm1-gap2-ceil3p0,ceiling,headhitter,sprint,1,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm1-gap3-ceil3p0,ceiling,headhitter,sprint,1,3,0.0,3.0,,,True,4.1566372864603425,1.2,0.9566372864603423, +ceiling-headhitter-sprint-mm1-gap4-ceil3p0,ceiling,headhitter,sprint,1,4,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm1-gap1-ceil2p5,ceiling,headhitter,sprint,1,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm1-gap2-ceil2p5,ceiling,headhitter,sprint,1,2,0.0,2.5,,,True,2.8119173623355342,0.7,0.6119173623355341, +ceiling-headhitter-sprint-mm1-gap3-ceil2p5,ceiling,headhitter,sprint,1,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm1-gap4-ceil2p5,ceiling,headhitter,sprint,1,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm1-gap1-ceil2p0,ceiling,headhitter,sprint,1,1,0.0,2.0,,,True,1.7750953286829638,0.19999999999999996,0.5750953286829639, +ceiling-headhitter-sprint-mm1-gap2-ceil2p0,ceiling,headhitter,sprint,1,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm1-gap3-ceil2p0,ceiling,headhitter,sprint,1,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm1-gap4-ceil2p0,ceiling,headhitter,sprint,1,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm1-gap1-ceil1p8125,ceiling,headhitter,sprint,1,1,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm1-gap2-ceil1p8125,ceiling,headhitter,sprint,1,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm1-gap3-ceil1p8125,ceiling,headhitter,sprint,1,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm1-gap4-ceil1p8125,ceiling,headhitter,sprint,1,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm2-gap1-ceil4p0,ceiling,headhitter,sprint,2,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm2-gap2-ceil4p0,ceiling,headhitter,sprint,2,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm2-gap3-ceil4p0,ceiling,headhitter,sprint,2,3,0.0,4.0,,,True,4.769275490524089,1.2522033525119995,1.569275490524089, +ceiling-headhitter-sprint-mm2-gap4-ceil4p0,ceiling,headhitter,sprint,2,4,0.0,4.0,,,True,4.769275490524089,1.2522033525119995,0.5692754905240891, +ceiling-headhitter-sprint-mm2-gap1-ceil3p0,ceiling,headhitter,sprint,2,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm2-gap2-ceil3p0,ceiling,headhitter,sprint,2,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm2-gap3-ceil3p0,ceiling,headhitter,sprint,2,3,0.0,3.0,,,True,4.421510012753032,1.2,1.2215100127530318, +ceiling-headhitter-sprint-mm2-gap4-ceil3p0,ceiling,headhitter,sprint,2,4,0.0,3.0,,,True,4.421510012753032,1.2,0.22151001275303184, +ceiling-headhitter-sprint-mm2-gap1-ceil2p5,ceiling,headhitter,sprint,2,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm2-gap2-ceil2p5,ceiling,headhitter,sprint,2,2,0.0,2.5,,,True,2.9926221398037094,0.7,0.7926221398037092, +ceiling-headhitter-sprint-mm2-gap3-ceil2p5,ceiling,headhitter,sprint,2,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm2-gap4-ceil2p5,ceiling,headhitter,sprint,2,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm2-gap1-ceil2p0,ceiling,headhitter,sprint,2,1,0.0,2.0,,,True,1.874293659546315,0.19999999999999996,0.6742936595463151, +ceiling-headhitter-sprint-mm2-gap2-ceil2p0,ceiling,headhitter,sprint,2,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm2-gap3-ceil2p0,ceiling,headhitter,sprint,2,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm2-gap4-ceil2p0,ceiling,headhitter,sprint,2,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm2-gap1-ceil1p8125,ceiling,headhitter,sprint,2,1,0.0,1.8125,,,True,1.2067611897704629,0.012499999999999956,0.0067611897704629165, +ceiling-headhitter-sprint-mm2-gap2-ceil1p8125,ceiling,headhitter,sprint,2,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm2-gap3-ceil1p8125,ceiling,headhitter,sprint,2,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm2-gap4-ceil1p8125,ceiling,headhitter,sprint,2,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm3-gap1-ceil4p0,ceiling,headhitter,sprint,3,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm3-gap2-ceil4p0,ceiling,headhitter,sprint,3,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm3-gap3-ceil4p0,ceiling,headhitter,sprint,3,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm3-gap4-ceil4p0,ceiling,headhitter,sprint,3,4,0.0,4.0,,,True,4.9231734716287,1.2522033525119995,0.7231734716286997, +ceiling-headhitter-sprint-mm3-gap1-ceil3p0,ceiling,headhitter,sprint,3,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm3-gap2-ceil3p0,ceiling,headhitter,sprint,3,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm3-gap3-ceil3p0,ceiling,headhitter,sprint,3,3,0.0,3.0,,,True,4.56613052130884,1.2,1.3661305213088397, +ceiling-headhitter-sprint-mm3-gap4-ceil3p0,ceiling,headhitter,sprint,3,4,0.0,3.0,,,True,4.56613052130884,1.2,0.3661305213088397, +ceiling-headhitter-sprint-mm3-gap1-ceil2p5,ceiling,headhitter,sprint,3,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm3-gap2-ceil2p5,ceiling,headhitter,sprint,3,2,0.0,2.5,,,True,3.091286948301333,0.7,0.891286948301333, +ceiling-headhitter-sprint-mm3-gap3-ceil2p5,ceiling,headhitter,sprint,3,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm3-gap4-ceil2p5,ceiling,headhitter,sprint,3,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm3-gap1-ceil2p0,ceiling,headhitter,sprint,3,1,0.0,2.0,,,True,1.928455948197705,0.19999999999999996,0.7284559481977051, +ceiling-headhitter-sprint-mm3-gap2-ceil2p0,ceiling,headhitter,sprint,3,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm3-gap3-ceil2p0,ceiling,headhitter,sprint,3,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm3-gap4-ceil2p0,ceiling,headhitter,sprint,3,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm3-gap1-ceil1p8125,ceiling,headhitter,sprint,3,1,0.0,1.8125,,,True,1.231000442014838,0.012499999999999956,0.03100044201483798, +ceiling-headhitter-sprint-mm3-gap2-ceil1p8125,ceiling,headhitter,sprint,3,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm3-gap3-ceil1p8125,ceiling,headhitter,sprint,3,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm3-gap4-ceil1p8125,ceiling,headhitter,sprint,3,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm4-gap1-ceil4p0,ceiling,headhitter,sprint,4,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm4-gap2-ceil4p0,ceiling,headhitter,sprint,4,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm4-gap3-ceil4p0,ceiling,headhitter,sprint,4,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm4-gap4-ceil4p0,ceiling,headhitter,sprint,4,4,0.0,4.0,,,True,5.007201769311817,1.2522033525119995,0.807201769311817, +ceiling-headhitter-sprint-mm4-gap1-ceil3p0,ceiling,headhitter,sprint,4,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm4-gap2-ceil3p0,ceiling,headhitter,sprint,4,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm4-gap3-ceil3p0,ceiling,headhitter,sprint,4,3,0.0,3.0,,,True,4.645093318980311,1.2,1.4450933189803106, +ceiling-headhitter-sprint-mm4-gap4-ceil3p0,ceiling,headhitter,sprint,4,4,0.0,3.0,,,True,4.645093318980311,1.2,0.44509331898031057, +ceiling-headhitter-sprint-mm4-gap1-ceil2p5,ceiling,headhitter,sprint,4,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm4-gap2-ceil2p5,ceiling,headhitter,sprint,4,2,0.0,2.5,,,True,3.145157933741036,0.7,0.9451579337410356, +ceiling-headhitter-sprint-mm4-gap3-ceil2p5,ceiling,headhitter,sprint,4,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm4-gap4-ceil2p5,ceiling,headhitter,sprint,4,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm4-gap1-ceil2p0,ceiling,headhitter,sprint,4,1,0.0,2.0,,,True,1.9580285578013639,0.19999999999999996,0.7580285578013639, +ceiling-headhitter-sprint-mm4-gap2-ceil2p0,ceiling,headhitter,sprint,4,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm4-gap3-ceil2p0,ceiling,headhitter,sprint,4,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm4-gap4-ceil2p0,ceiling,headhitter,sprint,4,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm4-gap1-ceil1p8125,ceiling,headhitter,sprint,4,1,0.0,1.8125,,,True,1.2442350737402668,0.012499999999999956,0.044235073740266806, +ceiling-headhitter-sprint-mm4-gap2-ceil1p8125,ceiling,headhitter,sprint,4,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm4-gap3-ceil1p8125,ceiling,headhitter,sprint,4,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm4-gap4-ceil1p8125,ceiling,headhitter,sprint,4,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm5-gap1-ceil4p0,ceiling,headhitter,sprint,5,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm5-gap2-ceil4p0,ceiling,headhitter,sprint,5,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm5-gap3-ceil4p0,ceiling,headhitter,sprint,5,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm5-gap4-ceil4p0,ceiling,headhitter,sprint,5,4,0.0,4.0,,,True,5.0530812198468,1.2522033525119995,0.8530812198467999, +ceiling-headhitter-sprint-mm5-gap1-ceil3p0,ceiling,headhitter,sprint,5,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm5-gap2-ceil3p0,ceiling,headhitter,sprint,5,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm5-gap3-ceil3p0,ceiling,headhitter,sprint,5,3,0.0,3.0,,,True,4.688207006508934,1.2,1.488207006508934, +ceiling-headhitter-sprint-mm5-gap4-ceil3p0,ceiling,headhitter,sprint,5,4,0.0,3.0,,,True,4.688207006508934,1.2,0.4882070065089339, +ceiling-headhitter-sprint-mm5-gap1-ceil2p5,ceiling,headhitter,sprint,5,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm5-gap2-ceil2p5,ceiling,headhitter,sprint,5,2,0.0,2.5,,,True,3.1745714917911134,0.7,0.9745714917911132, +ceiling-headhitter-sprint-mm5-gap3-ceil2p5,ceiling,headhitter,sprint,5,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm5-gap4-ceil2p5,ceiling,headhitter,sprint,5,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm5-gap1-ceil2p0,ceiling,headhitter,sprint,5,1,0.0,2.0,,,True,1.9741752026449617,0.19999999999999996,0.7741752026449618, +ceiling-headhitter-sprint-mm5-gap2-ceil2p0,ceiling,headhitter,sprint,5,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm5-gap3-ceil2p0,ceiling,headhitter,sprint,5,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm5-gap4-ceil2p0,ceiling,headhitter,sprint,5,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm5-gap1-ceil1p8125,ceiling,headhitter,sprint,5,1,0.0,1.8125,,,True,1.2514611826623507,0.012499999999999956,0.05146118266235078, +ceiling-headhitter-sprint-mm5-gap2-ceil1p8125,ceiling,headhitter,sprint,5,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm5-gap3-ceil1p8125,ceiling,headhitter,sprint,5,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm5-gap4-ceil1p8125,ceiling,headhitter,sprint,5,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm6-gap1-ceil4p0,ceiling,headhitter,sprint,6,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm6-gap2-ceil4p0,ceiling,headhitter,sprint,6,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm6-gap3-ceil4p0,ceiling,headhitter,sprint,6,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm6-gap4-ceil4p0,ceiling,headhitter,sprint,6,4,0.0,4.0,,,True,5.078131399838901,1.2522033525119995,0.8781313998389004, +ceiling-headhitter-sprint-mm6-gap1-ceil3p0,ceiling,headhitter,sprint,6,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm6-gap2-ceil3p0,ceiling,headhitter,sprint,6,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm6-gap3-ceil3p0,ceiling,headhitter,sprint,6,3,0.0,3.0,,,True,4.711747079899563,1.2,1.5117470798995631, +ceiling-headhitter-sprint-mm6-gap4-ceil3p0,ceiling,headhitter,sprint,6,4,0.0,3.0,,,True,4.711747079899563,1.2,0.5117470798995631, +ceiling-headhitter-sprint-mm6-gap1-ceil2p5,ceiling,headhitter,sprint,6,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm6-gap2-ceil2p5,ceiling,headhitter,sprint,6,2,0.0,2.5,,,True,3.1906312944864554,0.7,0.9906312944864553, +ceiling-headhitter-sprint-mm6-gap3-ceil2p5,ceiling,headhitter,sprint,6,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm6-gap4-ceil2p5,ceiling,headhitter,sprint,6,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm6-gap1-ceil2p0,ceiling,headhitter,sprint,6,1,0.0,2.0,,,True,1.982991270729566,0.19999999999999996,0.7829912707295661, +ceiling-headhitter-sprint-mm6-gap2-ceil2p0,ceiling,headhitter,sprint,6,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm6-gap3-ceil2p0,ceiling,headhitter,sprint,6,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm6-gap4-ceil2p0,ceiling,headhitter,sprint,6,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm6-gap1-ceil1p8125,ceiling,headhitter,sprint,6,1,0.0,1.8125,,,True,1.2554066381338087,0.012499999999999956,0.05540663813380875, +ceiling-headhitter-sprint-mm6-gap2-ceil1p8125,ceiling,headhitter,sprint,6,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm6-gap3-ceil1p8125,ceiling,headhitter,sprint,6,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm6-gap4-ceil1p8125,ceiling,headhitter,sprint,6,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm7-gap1-ceil4p0,ceiling,headhitter,sprint,7,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm7-gap2-ceil4p0,ceiling,headhitter,sprint,7,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm7-gap3-ceil4p0,ceiling,headhitter,sprint,7,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm7-gap4-ceil4p0,ceiling,headhitter,sprint,7,4,0.0,4.0,,,True,5.091808798114586,1.2522033525119995,0.8918087981145861, +ceiling-headhitter-sprint-mm7-gap1-ceil3p0,ceiling,headhitter,sprint,7,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm7-gap2-ceil3p0,ceiling,headhitter,sprint,7,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm7-gap3-ceil3p0,ceiling,headhitter,sprint,7,3,0.0,3.0,,,True,4.7245999599708455,1.2,1.5245999599708453, +ceiling-headhitter-sprint-mm7-gap4-ceil3p0,ceiling,headhitter,sprint,7,4,0.0,3.0,,,True,4.7245999599708455,1.2,0.5245999599708453, +ceiling-headhitter-sprint-mm7-gap1-ceil2p5,ceiling,headhitter,sprint,7,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm7-gap2-ceil2p5,ceiling,headhitter,sprint,7,2,0.0,2.5,,,True,3.1993999467581125,0.7,0.9993999467581123, +ceiling-headhitter-sprint-mm7-gap3-ceil2p5,ceiling,headhitter,sprint,7,3,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm7-gap4-ceil2p5,ceiling,headhitter,sprint,7,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm7-gap1-ceil2p0,ceiling,headhitter,sprint,7,1,0.0,2.0,,,True,1.9878048439037599,0.19999999999999996,0.7878048439037599, +ceiling-headhitter-sprint-mm7-gap2-ceil2p0,ceiling,headhitter,sprint,7,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm7-gap3-ceil2p0,ceiling,headhitter,sprint,7,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm7-gap4-ceil2p0,ceiling,headhitter,sprint,7,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm7-gap1-ceil1p8125,ceiling,headhitter,sprint,7,1,0.0,1.8125,,,True,1.2575608568212246,0.012499999999999956,0.05756085682122469, +ceiling-headhitter-sprint-mm7-gap2-ceil1p8125,ceiling,headhitter,sprint,7,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm7-gap3-ceil1p8125,ceiling,headhitter,sprint,7,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm7-gap4-ceil1p8125,ceiling,headhitter,sprint,7,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm8-gap1-ceil4p0,ceiling,headhitter,sprint,8,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm8-gap2-ceil4p0,ceiling,headhitter,sprint,8,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm8-gap3-ceil4p0,ceiling,headhitter,sprint,8,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm8-gap4-ceil4p0,ceiling,headhitter,sprint,8,4,0.0,4.0,,,True,5.099276657573112,1.2522033525119995,0.8992766575731119, +ceiling-headhitter-sprint-mm8-gap1-ceil3p0,ceiling,headhitter,sprint,8,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm8-gap2-ceil3p0,ceiling,headhitter,sprint,8,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm8-gap3-ceil3p0,ceiling,headhitter,sprint,8,3,0.0,3.0,,,True,4.731617632489766,1.2,1.5316176324897661, +ceiling-headhitter-sprint-mm8-gap4-ceil3p0,ceiling,headhitter,sprint,8,4,0.0,3.0,,,True,4.731617632489766,1.2,0.5316176324897661, +ceiling-headhitter-sprint-mm8-gap1-ceil2p5,ceiling,headhitter,sprint,8,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm8-gap2-ceil2p5,ceiling,headhitter,sprint,8,2,0.0,2.5,,,True,3.2041876308984367,0.7,1.0041876308984365, +ceiling-headhitter-sprint-mm8-gap3-ceil2p5,ceiling,headhitter,sprint,8,3,0.0,2.5,,,True,3.2041876308984367,0.7,0.004187630898436545, +ceiling-headhitter-sprint-mm8-gap4-ceil2p5,ceiling,headhitter,sprint,8,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm8-gap1-ceil2p0,ceiling,headhitter,sprint,8,1,0.0,2.0,,,True,1.9904330548568696,0.19999999999999996,0.7904330548568697, +ceiling-headhitter-sprint-mm8-gap2-ceil2p0,ceiling,headhitter,sprint,8,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm8-gap3-ceil2p0,ceiling,headhitter,sprint,8,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm8-gap4-ceil2p0,ceiling,headhitter,sprint,8,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm8-gap1-ceil1p8125,ceiling,headhitter,sprint,8,1,0.0,1.8125,,,True,1.2587370602245538,0.012499999999999956,0.05873706022455383, +ceiling-headhitter-sprint-mm8-gap2-ceil1p8125,ceiling,headhitter,sprint,8,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm8-gap3-ceil1p8125,ceiling,headhitter,sprint,8,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm8-gap4-ceil1p8125,ceiling,headhitter,sprint,8,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm9-gap1-ceil4p0,ceiling,headhitter,sprint,9,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm9-gap2-ceil4p0,ceiling,headhitter,sprint,9,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm9-gap3-ceil4p0,ceiling,headhitter,sprint,9,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm9-gap4-ceil4p0,ceiling,headhitter,sprint,9,4,0.0,4.0,,,True,5.103354108837465,1.2522033525119995,0.9033541088374646, +ceiling-headhitter-sprint-mm9-gap1-ceil3p0,ceiling,headhitter,sprint,9,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm9-gap2-ceil3p0,ceiling,headhitter,sprint,9,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm9-gap3-ceil3p0,ceiling,headhitter,sprint,9,3,0.0,3.0,,,True,4.735449281685096,1.2,1.5354492816850955, +ceiling-headhitter-sprint-mm9-gap4-ceil3p0,ceiling,headhitter,sprint,9,4,0.0,3.0,,,True,4.735449281685096,1.2,0.5354492816850955, +ceiling-headhitter-sprint-mm9-gap1-ceil2p5,ceiling,headhitter,sprint,9,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm9-gap2-ceil2p5,ceiling,headhitter,sprint,9,2,0.0,2.5,,,True,3.2068017064390544,0.7,1.0068017064390542, +ceiling-headhitter-sprint-mm9-gap3-ceil2p5,ceiling,headhitter,sprint,9,3,0.0,2.5,,,True,3.2068017064390544,0.7,0.0068017064390542, +ceiling-headhitter-sprint-mm9-gap4-ceil2p5,ceiling,headhitter,sprint,9,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm9-gap1-ceil2p0,ceiling,headhitter,sprint,9,1,0.0,2.0,,,True,1.9918680580372676,0.19999999999999996,0.7918680580372677, +ceiling-headhitter-sprint-mm9-gap2-ceil2p0,ceiling,headhitter,sprint,9,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm9-gap3-ceil2p0,ceiling,headhitter,sprint,9,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm9-gap4-ceil2p0,ceiling,headhitter,sprint,9,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm9-gap1-ceil1p8125,ceiling,headhitter,sprint,9,1,0.0,1.8125,,,True,1.2593792672827715,0.012499999999999956,0.05937926728277154, +ceiling-headhitter-sprint-mm9-gap2-ceil1p8125,ceiling,headhitter,sprint,9,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm9-gap3-ceil1p8125,ceiling,headhitter,sprint,9,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm9-gap4-ceil1p8125,ceiling,headhitter,sprint,9,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm10-gap1-ceil4p0,ceiling,headhitter,sprint,10,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm10-gap2-ceil4p0,ceiling,headhitter,sprint,10,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm10-gap3-ceil4p0,ceiling,headhitter,sprint,10,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm10-gap4-ceil4p0,ceiling,headhitter,sprint,10,4,0.0,4.0,,,True,5.105580397227805,1.2522033525119995,0.9055803972278049, +ceiling-headhitter-sprint-mm10-gap1-ceil3p0,ceiling,headhitter,sprint,10,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm10-gap2-ceil3p0,ceiling,headhitter,sprint,10,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm10-gap3-ceil3p0,ceiling,headhitter,sprint,10,3,0.0,3.0,,,True,4.737541362145748,1.2,1.537541362145748, +ceiling-headhitter-sprint-mm10-gap4-ceil3p0,ceiling,headhitter,sprint,10,4,0.0,3.0,,,True,4.737541362145748,1.2,0.5375413621457481, +ceiling-headhitter-sprint-mm10-gap1-ceil2p5,ceiling,headhitter,sprint,10,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm10-gap2-ceil2p5,ceiling,headhitter,sprint,10,2,0.0,2.5,,,True,3.2082289916842317,0.7,1.0082289916842315, +ceiling-headhitter-sprint-mm10-gap3-ceil2p5,ceiling,headhitter,sprint,10,3,0.0,2.5,,,True,3.2082289916842317,0.7,0.008228991684231524, +ceiling-headhitter-sprint-mm10-gap4-ceil2p5,ceiling,headhitter,sprint,10,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm10-gap1-ceil2p0,ceiling,headhitter,sprint,10,1,0.0,2.0,,,True,1.9926515697737652,0.19999999999999996,0.7926515697737653, +ceiling-headhitter-sprint-mm10-gap2-ceil2p0,ceiling,headhitter,sprint,10,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm10-gap3-ceil2p0,ceiling,headhitter,sprint,10,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm10-gap4-ceil2p0,ceiling,headhitter,sprint,10,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm10-gap1-ceil1p8125,ceiling,headhitter,sprint,10,1,0.0,1.8125,,,True,1.2597299123365584,0.012499999999999956,0.059729912336558444, +ceiling-headhitter-sprint-mm10-gap2-ceil1p8125,ceiling,headhitter,sprint,10,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm10-gap3-ceil1p8125,ceiling,headhitter,sprint,10,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm10-gap4-ceil1p8125,ceiling,headhitter,sprint,10,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm11-gap1-ceil4p0,ceiling,headhitter,sprint,11,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm11-gap2-ceil4p0,ceiling,headhitter,sprint,11,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm11-gap3-ceil4p0,ceiling,headhitter,sprint,11,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm11-gap4-ceil4p0,ceiling,headhitter,sprint,11,4,0.0,4.0,,,True,5.106795950688928,1.2522033525119995,0.9067959506889283, +ceiling-headhitter-sprint-mm11-gap1-ceil3p0,ceiling,headhitter,sprint,11,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm11-gap2-ceil3p0,ceiling,headhitter,sprint,11,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm11-gap3-ceil3p0,ceiling,headhitter,sprint,11,3,0.0,3.0,,,True,4.738683638077263,1.2,1.5386836380772628, +ceiling-headhitter-sprint-mm11-gap4-ceil3p0,ceiling,headhitter,sprint,11,4,0.0,3.0,,,True,4.738683638077263,1.2,0.5386836380772628, +ceiling-headhitter-sprint-mm11-gap1-ceil2p5,ceiling,headhitter,sprint,11,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm11-gap2-ceil2p5,ceiling,headhitter,sprint,11,2,0.0,2.5,,,True,3.2090082894280987,0.7,1.0090082894280985, +ceiling-headhitter-sprint-mm11-gap3-ceil2p5,ceiling,headhitter,sprint,11,3,0.0,2.5,,,True,3.2090082894280987,0.7,0.009008289428098504, +ceiling-headhitter-sprint-mm11-gap4-ceil2p5,ceiling,headhitter,sprint,11,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm11-gap1-ceil2p0,ceiling,headhitter,sprint,11,1,0.0,2.0,,,True,1.993079367181893,0.19999999999999996,0.793079367181893, +ceiling-headhitter-sprint-mm11-gap2-ceil2p0,ceiling,headhitter,sprint,11,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm11-gap3-ceil2p0,ceiling,headhitter,sprint,11,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm11-gap4-ceil2p0,ceiling,headhitter,sprint,11,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm11-gap1-ceil1p8125,ceiling,headhitter,sprint,11,1,0.0,1.8125,,,True,1.2599213645359262,0.012499999999999956,0.059921364535926225, +ceiling-headhitter-sprint-mm11-gap2-ceil1p8125,ceiling,headhitter,sprint,11,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm11-gap3-ceil1p8125,ceiling,headhitter,sprint,11,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm11-gap4-ceil1p8125,ceiling,headhitter,sprint,11,4,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm12-gap1-ceil4p0,ceiling,headhitter,sprint,12,1,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm12-gap2-ceil4p0,ceiling,headhitter,sprint,12,2,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm12-gap3-ceil4p0,ceiling,headhitter,sprint,12,3,0.0,4.0,,,False,,1.2522033525119995,, +ceiling-headhitter-sprint-mm12-gap4-ceil4p0,ceiling,headhitter,sprint,12,4,0.0,4.0,,,True,5.107459642878702,1.2522033525119995,0.9074596428787016, +ceiling-headhitter-sprint-mm12-gap1-ceil3p0,ceiling,headhitter,sprint,12,1,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm12-gap2-ceil3p0,ceiling,headhitter,sprint,12,2,0.0,3.0,,,False,,1.2,, +ceiling-headhitter-sprint-mm12-gap3-ceil3p0,ceiling,headhitter,sprint,12,3,0.0,3.0,,,True,4.73930732073587,1.2,1.5393073207358698, +ceiling-headhitter-sprint-mm12-gap4-ceil3p0,ceiling,headhitter,sprint,12,4,0.0,3.0,,,True,4.73930732073587,1.2,0.5393073207358698, +ceiling-headhitter-sprint-mm12-gap1-ceil2p5,ceiling,headhitter,sprint,12,1,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm12-gap2-ceil2p5,ceiling,headhitter,sprint,12,2,0.0,2.5,,,True,3.209433785996249,0.7,1.009433785996249, +ceiling-headhitter-sprint-mm12-gap3-ceil2p5,ceiling,headhitter,sprint,12,3,0.0,2.5,,,True,3.209433785996249,0.7,0.009433785996249, +ceiling-headhitter-sprint-mm12-gap4-ceil2p5,ceiling,headhitter,sprint,12,4,0.0,2.5,,,False,,0.7,, +ceiling-headhitter-sprint-mm12-gap1-ceil2p0,ceiling,headhitter,sprint,12,1,0.0,2.0,,,True,1.9933129445667304,0.19999999999999996,0.7933129445667304, +ceiling-headhitter-sprint-mm12-gap2-ceil2p0,ceiling,headhitter,sprint,12,2,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm12-gap3-ceil2p0,ceiling,headhitter,sprint,12,3,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm12-gap4-ceil2p0,ceiling,headhitter,sprint,12,4,0.0,2.0,,,False,,0.19999999999999996,, +ceiling-headhitter-sprint-mm12-gap1-ceil1p8125,ceiling,headhitter,sprint,12,1,0.0,1.8125,,,True,1.2600258974367808,0.012499999999999956,0.060025897436780884, +ceiling-headhitter-sprint-mm12-gap2-ceil1p8125,ceiling,headhitter,sprint,12,2,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm12-gap3-ceil1p8125,ceiling,headhitter,sprint,12,3,0.0,1.8125,,,False,,0.012499999999999956,, +ceiling-headhitter-sprint-mm12-gap4-ceil1p8125,ceiling,headhitter,sprint,12,4,0.0,1.8125,,,False,,0.012499999999999956,, +sidewall-flat-walk-mm0-gap0-dy0p0-wo0,sidewall,flat,walk,0,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap0-dy1p0-wo0,sidewall,ascend,walk,0,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap0-dym1p0-wo0,sidewall,descend,walk,0,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap0-dym2p0-wo0,sidewall,descend,walk,0,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap1-dy0p0-wo0,sidewall,flat,walk,0,1,0.0,,,0,True,2.489507369982936,1.2522033525119995,1.289507369982936, +sidewall-ascend-walk-mm0-gap1-dy1p0-wo0,sidewall,ascend,walk,0,1,1.0,,,0,True,1.9258590718663482,1.2522033525119995,0.7258590718663482, +sidewall-descend-walk-mm0-gap1-dym1p0-wo0,sidewall,descend,walk,0,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap1-dym2p0-wo0,sidewall,descend,walk,0,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap2-dy0p0-wo0,sidewall,flat,walk,0,2,0.0,,,0,True,2.489507369982936,1.2522033525119995,0.2895073699829358, +sidewall-ascend-walk-mm0-gap2-dy1p0-wo0,sidewall,ascend,walk,0,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap2-dym1p0-wo0,sidewall,descend,walk,0,2,-1.0,,,0,True,2.8624809325550244,1.2522033525119995,0.6624809325550243, +sidewall-descend-walk-mm0-gap2-dym2p0-wo0,sidewall,descend,walk,0,2,-2.0,,,0,True,3.169858896301496,1.2522033525119995,0.969858896301496, +sidewall-flat-walk-mm0-gap3-dy0p0-wo0,sidewall,flat,walk,0,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap3-dy1p0-wo0,sidewall,ascend,walk,0,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap3-dym1p0-wo0,sidewall,descend,walk,0,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap3-dym2p0-wo0,sidewall,descend,walk,0,3,-2.0,,,0,False,3.2045233782724227,1.2522033525119995,0.004523378272422551, +sidewall-flat-walk-mm0-gap4-dy0p0-wo0,sidewall,flat,walk,0,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap4-dy1p0-wo0,sidewall,ascend,walk,0,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap4-dym1p0-wo0,sidewall,descend,walk,0,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap4-dym2p0-wo0,sidewall,descend,walk,0,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap5-dy0p0-wo0,sidewall,flat,walk,0,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap5-dy1p0-wo0,sidewall,ascend,walk,0,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap5-dym1p0-wo0,sidewall,descend,walk,0,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap5-dym2p0-wo0,sidewall,descend,walk,0,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap6-dy0p0-wo0,sidewall,flat,walk,0,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap6-dy1p0-wo0,sidewall,ascend,walk,0,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap6-dym1p0-wo0,sidewall,descend,walk,0,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap6-dym2p0-wo0,sidewall,descend,walk,0,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap7-dy0p0-wo0,sidewall,flat,walk,0,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap7-dy1p0-wo0,sidewall,ascend,walk,0,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap7-dym1p0-wo0,sidewall,descend,walk,0,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap7-dym2p0-wo0,sidewall,descend,walk,0,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap0-dy0p0-wo1,sidewall,flat,walk,0,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap0-dy1p0-wo1,sidewall,ascend,walk,0,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap0-dym1p0-wo1,sidewall,descend,walk,0,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap0-dym2p0-wo1,sidewall,descend,walk,0,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap1-dy0p0-wo1,sidewall,flat,walk,0,1,0.0,,,1,True,2.489507369982936,1.2522033525119995,1.289507369982936, +sidewall-ascend-walk-mm0-gap1-dy1p0-wo1,sidewall,ascend,walk,0,1,1.0,,,1,True,1.9258590718663482,1.2522033525119995,0.7258590718663482, +sidewall-descend-walk-mm0-gap1-dym1p0-wo1,sidewall,descend,walk,0,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap1-dym2p0-wo1,sidewall,descend,walk,0,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap2-dy0p0-wo1,sidewall,flat,walk,0,2,0.0,,,1,True,2.489507369982936,1.2522033525119995,0.2895073699829358, +sidewall-ascend-walk-mm0-gap2-dy1p0-wo1,sidewall,ascend,walk,0,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap2-dym1p0-wo1,sidewall,descend,walk,0,2,-1.0,,,1,True,2.8624809325550244,1.2522033525119995,0.6624809325550243, +sidewall-descend-walk-mm0-gap2-dym2p0-wo1,sidewall,descend,walk,0,2,-2.0,,,1,True,3.169858896301496,1.2522033525119995,0.969858896301496, +sidewall-flat-walk-mm0-gap3-dy0p0-wo1,sidewall,flat,walk,0,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap3-dy1p0-wo1,sidewall,ascend,walk,0,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap3-dym1p0-wo1,sidewall,descend,walk,0,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap3-dym2p0-wo1,sidewall,descend,walk,0,3,-2.0,,,1,False,3.2045233782724227,1.2522033525119995,0.004523378272422551, +sidewall-flat-walk-mm0-gap4-dy0p0-wo1,sidewall,flat,walk,0,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap4-dy1p0-wo1,sidewall,ascend,walk,0,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap4-dym1p0-wo1,sidewall,descend,walk,0,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap4-dym2p0-wo1,sidewall,descend,walk,0,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap5-dy0p0-wo1,sidewall,flat,walk,0,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap5-dy1p0-wo1,sidewall,ascend,walk,0,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap5-dym1p0-wo1,sidewall,descend,walk,0,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap5-dym2p0-wo1,sidewall,descend,walk,0,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap6-dy0p0-wo1,sidewall,flat,walk,0,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap6-dy1p0-wo1,sidewall,ascend,walk,0,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap6-dym1p0-wo1,sidewall,descend,walk,0,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap6-dym2p0-wo1,sidewall,descend,walk,0,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm0-gap7-dy0p0-wo1,sidewall,flat,walk,0,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm0-gap7-dy1p0-wo1,sidewall,ascend,walk,0,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap7-dym1p0-wo1,sidewall,descend,walk,0,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm0-gap7-dym2p0-wo1,sidewall,descend,walk,0,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap0-dy0p0-wo0,sidewall,flat,walk,1,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap0-dy1p0-wo0,sidewall,ascend,walk,1,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap0-dym1p0-wo0,sidewall,descend,walk,1,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap0-dym2p0-wo0,sidewall,descend,walk,1,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap1-dy0p0-wo0,sidewall,flat,walk,1,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap1-dy1p0-wo0,sidewall,ascend,walk,1,1,1.0,,,0,True,2.340983683669408,1.2522033525119995,1.1409836836694078, +sidewall-descend-walk-mm1-gap1-dym1p0-wo0,sidewall,descend,walk,1,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap1-dym2p0-wo0,sidewall,descend,walk,1,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap2-dy0p0-wo0,sidewall,flat,walk,1,2,0.0,,,0,True,2.9978998244796653,1.2522033525119995,0.7978998244796651, +sidewall-ascend-walk-mm1-gap2-dy1p0-wo0,sidewall,ascend,walk,1,2,1.0,,,0,True,2.340983683669408,1.2522033525119995,0.1409836836694076, +sidewall-descend-walk-mm1-gap2-dym1p0-wo0,sidewall,descend,walk,1,2,-1.0,,,0,True,3.41715856961436,1.2522033525119995,1.2171585696143596, +sidewall-descend-walk-mm1-gap2-dym2p0-wo0,sidewall,descend,walk,1,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap3-dy0p0-wo0,sidewall,flat,walk,1,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap3-dy1p0-wo0,sidewall,ascend,walk,1,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap3-dym1p0-wo0,sidewall,descend,walk,1,3,-1.0,,,0,True,3.41715856961436,1.2522033525119995,0.21715856961435964, +sidewall-descend-walk-mm1-gap3-dym2p0-wo0,sidewall,descend,walk,1,3,-2.0,,,0,True,3.756018889971772,1.2522033525119995,0.5560188899717717, +sidewall-flat-walk-mm1-gap4-dy0p0-wo0,sidewall,flat,walk,1,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap4-dy1p0-wo0,sidewall,ascend,walk,1,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap4-dym1p0-wo0,sidewall,descend,walk,1,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap4-dym2p0-wo0,sidewall,descend,walk,1,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap5-dy0p0-wo0,sidewall,flat,walk,1,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap5-dy1p0-wo0,sidewall,ascend,walk,1,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap5-dym1p0-wo0,sidewall,descend,walk,1,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap5-dym2p0-wo0,sidewall,descend,walk,1,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap6-dy0p0-wo0,sidewall,flat,walk,1,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap6-dy1p0-wo0,sidewall,ascend,walk,1,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap6-dym1p0-wo0,sidewall,descend,walk,1,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap6-dym2p0-wo0,sidewall,descend,walk,1,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap7-dy0p0-wo0,sidewall,flat,walk,1,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap7-dy1p0-wo0,sidewall,ascend,walk,1,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap7-dym1p0-wo0,sidewall,descend,walk,1,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap7-dym2p0-wo0,sidewall,descend,walk,1,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap0-dy0p0-wo1,sidewall,flat,walk,1,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap0-dy1p0-wo1,sidewall,ascend,walk,1,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap0-dym1p0-wo1,sidewall,descend,walk,1,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap0-dym2p0-wo1,sidewall,descend,walk,1,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap1-dy0p0-wo1,sidewall,flat,walk,1,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap1-dy1p0-wo1,sidewall,ascend,walk,1,1,1.0,,,1,True,2.340983683669408,1.2522033525119995,1.1409836836694078, +sidewall-descend-walk-mm1-gap1-dym1p0-wo1,sidewall,descend,walk,1,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap1-dym2p0-wo1,sidewall,descend,walk,1,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap2-dy0p0-wo1,sidewall,flat,walk,1,2,0.0,,,1,True,2.9978998244796653,1.2522033525119995,0.7978998244796651, +sidewall-ascend-walk-mm1-gap2-dy1p0-wo1,sidewall,ascend,walk,1,2,1.0,,,1,True,2.340983683669408,1.2522033525119995,0.1409836836694076, +sidewall-descend-walk-mm1-gap2-dym1p0-wo1,sidewall,descend,walk,1,2,-1.0,,,1,True,3.41715856961436,1.2522033525119995,1.2171585696143596, +sidewall-descend-walk-mm1-gap2-dym2p0-wo1,sidewall,descend,walk,1,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap3-dy0p0-wo1,sidewall,flat,walk,1,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap3-dy1p0-wo1,sidewall,ascend,walk,1,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap3-dym1p0-wo1,sidewall,descend,walk,1,3,-1.0,,,1,True,3.41715856961436,1.2522033525119995,0.21715856961435964, +sidewall-descend-walk-mm1-gap3-dym2p0-wo1,sidewall,descend,walk,1,3,-2.0,,,1,True,3.756018889971772,1.2522033525119995,0.5560188899717717, +sidewall-flat-walk-mm1-gap4-dy0p0-wo1,sidewall,flat,walk,1,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap4-dy1p0-wo1,sidewall,ascend,walk,1,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap4-dym1p0-wo1,sidewall,descend,walk,1,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap4-dym2p0-wo1,sidewall,descend,walk,1,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap5-dy0p0-wo1,sidewall,flat,walk,1,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap5-dy1p0-wo1,sidewall,ascend,walk,1,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap5-dym1p0-wo1,sidewall,descend,walk,1,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap5-dym2p0-wo1,sidewall,descend,walk,1,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap6-dy0p0-wo1,sidewall,flat,walk,1,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap6-dy1p0-wo1,sidewall,ascend,walk,1,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap6-dym1p0-wo1,sidewall,descend,walk,1,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap6-dym2p0-wo1,sidewall,descend,walk,1,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm1-gap7-dy0p0-wo1,sidewall,flat,walk,1,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm1-gap7-dy1p0-wo1,sidewall,ascend,walk,1,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap7-dym1p0-wo1,sidewall,descend,walk,1,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm1-gap7-dym2p0-wo1,sidewall,descend,walk,1,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap0-dy0p0-wo0,sidewall,flat,walk,2,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap0-dy1p0-wo0,sidewall,ascend,walk,2,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap0-dym1p0-wo0,sidewall,descend,walk,2,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap0-dym2p0-wo0,sidewall,descend,walk,2,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap1-dy0p0-wo0,sidewall,flat,walk,2,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap1-dy1p0-wo0,sidewall,ascend,walk,2,1,1.0,,,0,True,2.567641721713879,1.2522033525119995,1.367641721713879, +sidewall-descend-walk-mm2-gap1-dym1p0-wo0,sidewall,descend,walk,2,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap1-dym2p0-wo0,sidewall,descend,walk,2,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap2-dy0p0-wo0,sidewall,flat,walk,2,2,0.0,,,0,True,3.2754821046348814,1.2522033525119995,1.0754821046348813, +sidewall-ascend-walk-mm2-gap2-dy1p0-wo0,sidewall,ascend,walk,2,2,1.0,,,0,True,2.567641721713879,1.2522033525119995,0.36764172171387877, +sidewall-descend-walk-mm2-gap2-dym1p0-wo0,sidewall,descend,walk,2,2,-1.0,,,0,True,3.720012559448759,1.2522033525119995,1.5200125594487588, +sidewall-descend-walk-mm2-gap2-dym2p0-wo0,sidewall,descend,walk,2,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap3-dy0p0-wo0,sidewall,flat,walk,2,3,0.0,,,0,True,3.2754821046348814,1.2522033525119995,0.07548210463488125, +sidewall-ascend-walk-mm2-gap3-dy1p0-wo0,sidewall,ascend,walk,2,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap3-dym1p0-wo0,sidewall,descend,walk,2,3,-1.0,,,0,True,3.720012559448759,1.2522033525119995,0.5200125594487588, +sidewall-descend-walk-mm2-gap3-dym2p0-wo0,sidewall,descend,walk,2,3,-2.0,,,0,True,4.076062246515744,1.2522033525119995,0.8760622465157439, +sidewall-flat-walk-mm2-gap4-dy0p0-wo0,sidewall,flat,walk,2,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap4-dy1p0-wo0,sidewall,ascend,walk,2,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap4-dym1p0-wo0,sidewall,descend,walk,2,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap4-dym2p0-wo0,sidewall,descend,walk,2,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap5-dy0p0-wo0,sidewall,flat,walk,2,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap5-dy1p0-wo0,sidewall,ascend,walk,2,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap5-dym1p0-wo0,sidewall,descend,walk,2,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap5-dym2p0-wo0,sidewall,descend,walk,2,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap6-dy0p0-wo0,sidewall,flat,walk,2,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap6-dy1p0-wo0,sidewall,ascend,walk,2,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap6-dym1p0-wo0,sidewall,descend,walk,2,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap6-dym2p0-wo0,sidewall,descend,walk,2,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap7-dy0p0-wo0,sidewall,flat,walk,2,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap7-dy1p0-wo0,sidewall,ascend,walk,2,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap7-dym1p0-wo0,sidewall,descend,walk,2,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap7-dym2p0-wo0,sidewall,descend,walk,2,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap0-dy0p0-wo1,sidewall,flat,walk,2,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap0-dy1p0-wo1,sidewall,ascend,walk,2,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap0-dym1p0-wo1,sidewall,descend,walk,2,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap0-dym2p0-wo1,sidewall,descend,walk,2,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap1-dy0p0-wo1,sidewall,flat,walk,2,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap1-dy1p0-wo1,sidewall,ascend,walk,2,1,1.0,,,1,True,2.567641721713879,1.2522033525119995,1.367641721713879, +sidewall-descend-walk-mm2-gap1-dym1p0-wo1,sidewall,descend,walk,2,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap1-dym2p0-wo1,sidewall,descend,walk,2,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap2-dy0p0-wo1,sidewall,flat,walk,2,2,0.0,,,1,True,3.2754821046348814,1.2522033525119995,1.0754821046348813, +sidewall-ascend-walk-mm2-gap2-dy1p0-wo1,sidewall,ascend,walk,2,2,1.0,,,1,True,2.567641721713879,1.2522033525119995,0.36764172171387877, +sidewall-descend-walk-mm2-gap2-dym1p0-wo1,sidewall,descend,walk,2,2,-1.0,,,1,True,3.720012559448759,1.2522033525119995,1.5200125594487588, +sidewall-descend-walk-mm2-gap2-dym2p0-wo1,sidewall,descend,walk,2,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap3-dy0p0-wo1,sidewall,flat,walk,2,3,0.0,,,1,True,3.2754821046348814,1.2522033525119995,0.07548210463488125, +sidewall-ascend-walk-mm2-gap3-dy1p0-wo1,sidewall,ascend,walk,2,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap3-dym1p0-wo1,sidewall,descend,walk,2,3,-1.0,,,1,True,3.720012559448759,1.2522033525119995,0.5200125594487588, +sidewall-descend-walk-mm2-gap3-dym2p0-wo1,sidewall,descend,walk,2,3,-2.0,,,1,True,4.076062246515744,1.2522033525119995,0.8760622465157439, +sidewall-flat-walk-mm2-gap4-dy0p0-wo1,sidewall,flat,walk,2,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap4-dy1p0-wo1,sidewall,ascend,walk,2,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap4-dym1p0-wo1,sidewall,descend,walk,2,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap4-dym2p0-wo1,sidewall,descend,walk,2,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap5-dy0p0-wo1,sidewall,flat,walk,2,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap5-dy1p0-wo1,sidewall,ascend,walk,2,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap5-dym1p0-wo1,sidewall,descend,walk,2,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap5-dym2p0-wo1,sidewall,descend,walk,2,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap6-dy0p0-wo1,sidewall,flat,walk,2,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap6-dy1p0-wo1,sidewall,ascend,walk,2,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap6-dym1p0-wo1,sidewall,descend,walk,2,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap6-dym2p0-wo1,sidewall,descend,walk,2,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm2-gap7-dy0p0-wo1,sidewall,flat,walk,2,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm2-gap7-dy1p0-wo1,sidewall,ascend,walk,2,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap7-dym1p0-wo1,sidewall,descend,walk,2,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm2-gap7-dym2p0-wo1,sidewall,descend,walk,2,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap0-dy0p0-wo0,sidewall,flat,walk,3,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap0-dy1p0-wo0,sidewall,ascend,walk,3,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap0-dym1p0-wo0,sidewall,descend,walk,3,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap0-dym2p0-wo0,sidewall,descend,walk,3,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap1-dy0p0-wo0,sidewall,flat,walk,3,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap1-dy1p0-wo0,sidewall,ascend,walk,3,1,1.0,,,0,True,2.6913970104861606,1.2522033525119995,1.4913970104861607, +sidewall-descend-walk-mm3-gap1-dym1p0-wo0,sidewall,descend,walk,3,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap1-dym2p0-wo0,sidewall,descend,walk,3,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap2-dy0p0-wo0,sidewall,flat,walk,3,2,0.0,,,0,True,3.427042029599629,1.2522033525119995,1.227042029599629, +sidewall-ascend-walk-mm3-gap2-dy1p0-wo0,sidewall,ascend,walk,3,2,1.0,,,0,True,2.6913970104861606,1.2522033525119995,0.49139701048616047, +sidewall-descend-walk-mm3-gap2-dym1p0-wo0,sidewall,descend,walk,3,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap2-dym2p0-wo0,sidewall,descend,walk,3,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap3-dy0p0-wo0,sidewall,flat,walk,3,3,0.0,,,0,True,3.427042029599629,1.2522033525119995,0.22704202959962894, +sidewall-ascend-walk-mm3-gap3-dy1p0-wo0,sidewall,ascend,walk,3,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap3-dym1p0-wo0,sidewall,descend,walk,3,3,-1.0,,,0,True,3.8853708378983405,1.2522033525119995,0.6853708378983403, +sidewall-descend-walk-mm3-gap3-dym2p0-wo0,sidewall,descend,walk,3,3,-2.0,,,0,True,4.250805919188753,1.2522033525119995,1.050805919188753, +sidewall-flat-walk-mm3-gap4-dy0p0-wo0,sidewall,flat,walk,3,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap4-dy1p0-wo0,sidewall,ascend,walk,3,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap4-dym1p0-wo0,sidewall,descend,walk,3,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap4-dym2p0-wo0,sidewall,descend,walk,3,4,-2.0,,,0,True,4.250805919188753,1.2522033525119995,0.0508059191887531, +sidewall-flat-walk-mm3-gap5-dy0p0-wo0,sidewall,flat,walk,3,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap5-dy1p0-wo0,sidewall,ascend,walk,3,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap5-dym1p0-wo0,sidewall,descend,walk,3,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap5-dym2p0-wo0,sidewall,descend,walk,3,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap6-dy0p0-wo0,sidewall,flat,walk,3,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap6-dy1p0-wo0,sidewall,ascend,walk,3,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap6-dym1p0-wo0,sidewall,descend,walk,3,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap6-dym2p0-wo0,sidewall,descend,walk,3,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap7-dy0p0-wo0,sidewall,flat,walk,3,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap7-dy1p0-wo0,sidewall,ascend,walk,3,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap7-dym1p0-wo0,sidewall,descend,walk,3,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap7-dym2p0-wo0,sidewall,descend,walk,3,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap0-dy0p0-wo1,sidewall,flat,walk,3,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap0-dy1p0-wo1,sidewall,ascend,walk,3,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap0-dym1p0-wo1,sidewall,descend,walk,3,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap0-dym2p0-wo1,sidewall,descend,walk,3,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap1-dy0p0-wo1,sidewall,flat,walk,3,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap1-dy1p0-wo1,sidewall,ascend,walk,3,1,1.0,,,1,True,2.6913970104861606,1.2522033525119995,1.4913970104861607, +sidewall-descend-walk-mm3-gap1-dym1p0-wo1,sidewall,descend,walk,3,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap1-dym2p0-wo1,sidewall,descend,walk,3,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap2-dy0p0-wo1,sidewall,flat,walk,3,2,0.0,,,1,True,3.427042029599629,1.2522033525119995,1.227042029599629, +sidewall-ascend-walk-mm3-gap2-dy1p0-wo1,sidewall,ascend,walk,3,2,1.0,,,1,True,2.6913970104861606,1.2522033525119995,0.49139701048616047, +sidewall-descend-walk-mm3-gap2-dym1p0-wo1,sidewall,descend,walk,3,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap2-dym2p0-wo1,sidewall,descend,walk,3,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap3-dy0p0-wo1,sidewall,flat,walk,3,3,0.0,,,1,True,3.427042029599629,1.2522033525119995,0.22704202959962894, +sidewall-ascend-walk-mm3-gap3-dy1p0-wo1,sidewall,ascend,walk,3,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap3-dym1p0-wo1,sidewall,descend,walk,3,3,-1.0,,,1,True,3.8853708378983405,1.2522033525119995,0.6853708378983403, +sidewall-descend-walk-mm3-gap3-dym2p0-wo1,sidewall,descend,walk,3,3,-2.0,,,1,True,4.250805919188753,1.2522033525119995,1.050805919188753, +sidewall-flat-walk-mm3-gap4-dy0p0-wo1,sidewall,flat,walk,3,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap4-dy1p0-wo1,sidewall,ascend,walk,3,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap4-dym1p0-wo1,sidewall,descend,walk,3,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap4-dym2p0-wo1,sidewall,descend,walk,3,4,-2.0,,,1,True,4.250805919188753,1.2522033525119995,0.0508059191887531, +sidewall-flat-walk-mm3-gap5-dy0p0-wo1,sidewall,flat,walk,3,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap5-dy1p0-wo1,sidewall,ascend,walk,3,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap5-dym1p0-wo1,sidewall,descend,walk,3,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap5-dym2p0-wo1,sidewall,descend,walk,3,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap6-dy0p0-wo1,sidewall,flat,walk,3,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap6-dy1p0-wo1,sidewall,ascend,walk,3,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap6-dym1p0-wo1,sidewall,descend,walk,3,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap6-dym2p0-wo1,sidewall,descend,walk,3,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm3-gap7-dy0p0-wo1,sidewall,flat,walk,3,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm3-gap7-dy1p0-wo1,sidewall,ascend,walk,3,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap7-dym1p0-wo1,sidewall,descend,walk,3,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm3-gap7-dym2p0-wo1,sidewall,descend,walk,3,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap0-dy0p0-wo0,sidewall,flat,walk,4,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap0-dy1p0-wo0,sidewall,ascend,walk,4,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap0-dym1p0-wo0,sidewall,descend,walk,4,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap0-dym2p0-wo0,sidewall,descend,walk,4,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap1-dy0p0-wo0,sidewall,flat,walk,4,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap1-dy1p0-wo0,sidewall,ascend,walk,4,1,1.0,,,0,True,2.758967398155826,1.2522033525119995,1.5589673981558259, +sidewall-descend-walk-mm4-gap1-dym1p0-wo0,sidewall,descend,walk,4,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap1-dym2p0-wo0,sidewall,descend,walk,4,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap2-dy0p0-wo0,sidewall,flat,walk,4,2,0.0,,,0,True,3.509793748630381,1.2522033525119995,1.3097937486303808, +sidewall-ascend-walk-mm4-gap2-dy1p0-wo0,sidewall,ascend,walk,4,2,1.0,,,0,True,2.758967398155826,1.2522033525119995,0.5589673981558256, +sidewall-descend-walk-mm4-gap2-dym1p0-wo0,sidewall,descend,walk,4,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap2-dym2p0-wo0,sidewall,descend,walk,4,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap3-dy0p0-wo0,sidewall,flat,walk,4,3,0.0,,,0,True,3.509793748630381,1.2522033525119995,0.3097937486303808, +sidewall-ascend-walk-mm4-gap3-dy1p0-wo0,sidewall,ascend,walk,4,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap3-dym1p0-wo0,sidewall,descend,walk,4,3,-1.0,,,0,True,3.975656457931811,1.2522033525119995,0.7756564579318108, +sidewall-descend-walk-mm4-gap3-dym2p0-wo0,sidewall,descend,walk,4,3,-2.0,,,0,True,4.346215964468215,1.2522033525119995,1.1462159644682144, +sidewall-flat-walk-mm4-gap4-dy0p0-wo0,sidewall,flat,walk,4,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap4-dy1p0-wo0,sidewall,ascend,walk,4,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap4-dym1p0-wo0,sidewall,descend,walk,4,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap4-dym2p0-wo0,sidewall,descend,walk,4,4,-2.0,,,0,True,4.346215964468215,1.2522033525119995,0.1462159644682144, +sidewall-flat-walk-mm4-gap5-dy0p0-wo0,sidewall,flat,walk,4,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap5-dy1p0-wo0,sidewall,ascend,walk,4,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap5-dym1p0-wo0,sidewall,descend,walk,4,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap5-dym2p0-wo0,sidewall,descend,walk,4,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap6-dy0p0-wo0,sidewall,flat,walk,4,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap6-dy1p0-wo0,sidewall,ascend,walk,4,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap6-dym1p0-wo0,sidewall,descend,walk,4,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap6-dym2p0-wo0,sidewall,descend,walk,4,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap7-dy0p0-wo0,sidewall,flat,walk,4,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap7-dy1p0-wo0,sidewall,ascend,walk,4,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap7-dym1p0-wo0,sidewall,descend,walk,4,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap7-dym2p0-wo0,sidewall,descend,walk,4,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap0-dy0p0-wo1,sidewall,flat,walk,4,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap0-dy1p0-wo1,sidewall,ascend,walk,4,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap0-dym1p0-wo1,sidewall,descend,walk,4,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap0-dym2p0-wo1,sidewall,descend,walk,4,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap1-dy0p0-wo1,sidewall,flat,walk,4,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap1-dy1p0-wo1,sidewall,ascend,walk,4,1,1.0,,,1,True,2.758967398155826,1.2522033525119995,1.5589673981558259, +sidewall-descend-walk-mm4-gap1-dym1p0-wo1,sidewall,descend,walk,4,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap1-dym2p0-wo1,sidewall,descend,walk,4,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap2-dy0p0-wo1,sidewall,flat,walk,4,2,0.0,,,1,True,3.509793748630381,1.2522033525119995,1.3097937486303808, +sidewall-ascend-walk-mm4-gap2-dy1p0-wo1,sidewall,ascend,walk,4,2,1.0,,,1,True,2.758967398155826,1.2522033525119995,0.5589673981558256, +sidewall-descend-walk-mm4-gap2-dym1p0-wo1,sidewall,descend,walk,4,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap2-dym2p0-wo1,sidewall,descend,walk,4,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap3-dy0p0-wo1,sidewall,flat,walk,4,3,0.0,,,1,True,3.509793748630381,1.2522033525119995,0.3097937486303808, +sidewall-ascend-walk-mm4-gap3-dy1p0-wo1,sidewall,ascend,walk,4,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap3-dym1p0-wo1,sidewall,descend,walk,4,3,-1.0,,,1,True,3.975656457931811,1.2522033525119995,0.7756564579318108, +sidewall-descend-walk-mm4-gap3-dym2p0-wo1,sidewall,descend,walk,4,3,-2.0,,,1,True,4.346215964468215,1.2522033525119995,1.1462159644682144, +sidewall-flat-walk-mm4-gap4-dy0p0-wo1,sidewall,flat,walk,4,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap4-dy1p0-wo1,sidewall,ascend,walk,4,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap4-dym1p0-wo1,sidewall,descend,walk,4,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap4-dym2p0-wo1,sidewall,descend,walk,4,4,-2.0,,,1,True,4.346215964468215,1.2522033525119995,0.1462159644682144, +sidewall-flat-walk-mm4-gap5-dy0p0-wo1,sidewall,flat,walk,4,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap5-dy1p0-wo1,sidewall,ascend,walk,4,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap5-dym1p0-wo1,sidewall,descend,walk,4,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap5-dym2p0-wo1,sidewall,descend,walk,4,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap6-dy0p0-wo1,sidewall,flat,walk,4,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap6-dy1p0-wo1,sidewall,ascend,walk,4,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap6-dym1p0-wo1,sidewall,descend,walk,4,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap6-dym2p0-wo1,sidewall,descend,walk,4,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm4-gap7-dy0p0-wo1,sidewall,flat,walk,4,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm4-gap7-dy1p0-wo1,sidewall,ascend,walk,4,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap7-dym1p0-wo1,sidewall,descend,walk,4,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm4-gap7-dym2p0-wo1,sidewall,descend,walk,4,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap0-dy0p0-wo0,sidewall,flat,walk,5,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap0-dy1p0-wo0,sidewall,ascend,walk,5,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap0-dym1p0-wo0,sidewall,descend,walk,5,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap0-dym2p0-wo0,sidewall,descend,walk,5,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap1-dy0p0-wo0,sidewall,flat,walk,5,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap1-dy1p0-wo0,sidewall,ascend,walk,5,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap1-dym1p0-wo0,sidewall,descend,walk,5,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap1-dym2p0-wo0,sidewall,descend,walk,5,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap2-dy0p0-wo0,sidewall,flat,walk,5,2,0.0,,,0,True,3.554976187221172,1.2522033525119995,1.3549761872211716, +sidewall-ascend-walk-mm5-gap2-dy1p0-wo0,sidewall,ascend,walk,5,2,1.0,,,0,True,2.7958608298234635,1.2522033525119995,0.5958608298234633, +sidewall-descend-walk-mm5-gap2-dym1p0-wo0,sidewall,descend,walk,5,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap2-dym2p0-wo0,sidewall,descend,walk,5,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap3-dy0p0-wo0,sidewall,flat,walk,5,3,0.0,,,0,True,3.554976187221172,1.2522033525119995,0.35497618722117164, +sidewall-ascend-walk-mm5-gap3-dy1p0-wo0,sidewall,ascend,walk,5,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap3-dym1p0-wo0,sidewall,descend,walk,5,3,-1.0,,,0,True,4.024952406470087,1.2522033525119995,0.8249524064700866, +sidewall-descend-walk-mm5-gap3-dym2p0-wo0,sidewall,descend,walk,5,3,-2.0,,,0,True,4.3983098491908015,1.2522033525119995,1.1983098491908013, +sidewall-flat-walk-mm5-gap4-dy0p0-wo0,sidewall,flat,walk,5,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap4-dy1p0-wo0,sidewall,ascend,walk,5,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap4-dym1p0-wo0,sidewall,descend,walk,5,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap4-dym2p0-wo0,sidewall,descend,walk,5,4,-2.0,,,0,True,4.3983098491908015,1.2522033525119995,0.1983098491908013, +sidewall-flat-walk-mm5-gap5-dy0p0-wo0,sidewall,flat,walk,5,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap5-dy1p0-wo0,sidewall,ascend,walk,5,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap5-dym1p0-wo0,sidewall,descend,walk,5,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap5-dym2p0-wo0,sidewall,descend,walk,5,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap6-dy0p0-wo0,sidewall,flat,walk,5,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap6-dy1p0-wo0,sidewall,ascend,walk,5,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap6-dym1p0-wo0,sidewall,descend,walk,5,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap6-dym2p0-wo0,sidewall,descend,walk,5,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap7-dy0p0-wo0,sidewall,flat,walk,5,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap7-dy1p0-wo0,sidewall,ascend,walk,5,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap7-dym1p0-wo0,sidewall,descend,walk,5,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap7-dym2p0-wo0,sidewall,descend,walk,5,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap0-dy0p0-wo1,sidewall,flat,walk,5,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap0-dy1p0-wo1,sidewall,ascend,walk,5,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap0-dym1p0-wo1,sidewall,descend,walk,5,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap0-dym2p0-wo1,sidewall,descend,walk,5,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap1-dy0p0-wo1,sidewall,flat,walk,5,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap1-dy1p0-wo1,sidewall,ascend,walk,5,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap1-dym1p0-wo1,sidewall,descend,walk,5,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap1-dym2p0-wo1,sidewall,descend,walk,5,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap2-dy0p0-wo1,sidewall,flat,walk,5,2,0.0,,,1,True,3.554976187221172,1.2522033525119995,1.3549761872211716, +sidewall-ascend-walk-mm5-gap2-dy1p0-wo1,sidewall,ascend,walk,5,2,1.0,,,1,True,2.7958608298234635,1.2522033525119995,0.5958608298234633, +sidewall-descend-walk-mm5-gap2-dym1p0-wo1,sidewall,descend,walk,5,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap2-dym2p0-wo1,sidewall,descend,walk,5,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap3-dy0p0-wo1,sidewall,flat,walk,5,3,0.0,,,1,True,3.554976187221172,1.2522033525119995,0.35497618722117164, +sidewall-ascend-walk-mm5-gap3-dy1p0-wo1,sidewall,ascend,walk,5,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap3-dym1p0-wo1,sidewall,descend,walk,5,3,-1.0,,,1,True,4.024952406470087,1.2522033525119995,0.8249524064700866, +sidewall-descend-walk-mm5-gap3-dym2p0-wo1,sidewall,descend,walk,5,3,-2.0,,,1,True,4.3983098491908015,1.2522033525119995,1.1983098491908013, +sidewall-flat-walk-mm5-gap4-dy0p0-wo1,sidewall,flat,walk,5,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap4-dy1p0-wo1,sidewall,ascend,walk,5,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap4-dym1p0-wo1,sidewall,descend,walk,5,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap4-dym2p0-wo1,sidewall,descend,walk,5,4,-2.0,,,1,True,4.3983098491908015,1.2522033525119995,0.1983098491908013, +sidewall-flat-walk-mm5-gap5-dy0p0-wo1,sidewall,flat,walk,5,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap5-dy1p0-wo1,sidewall,ascend,walk,5,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap5-dym1p0-wo1,sidewall,descend,walk,5,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap5-dym2p0-wo1,sidewall,descend,walk,5,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap6-dy0p0-wo1,sidewall,flat,walk,5,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap6-dy1p0-wo1,sidewall,ascend,walk,5,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap6-dym1p0-wo1,sidewall,descend,walk,5,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap6-dym2p0-wo1,sidewall,descend,walk,5,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm5-gap7-dy0p0-wo1,sidewall,flat,walk,5,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm5-gap7-dy1p0-wo1,sidewall,ascend,walk,5,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap7-dym1p0-wo1,sidewall,descend,walk,5,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm5-gap7-dym2p0-wo1,sidewall,descend,walk,5,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap0-dy0p0-wo0,sidewall,flat,walk,6,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap0-dy1p0-wo0,sidewall,ascend,walk,6,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap0-dym1p0-wo0,sidewall,descend,walk,6,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap0-dym2p0-wo0,sidewall,descend,walk,6,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap1-dy0p0-wo0,sidewall,flat,walk,6,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap1-dy1p0-wo0,sidewall,ascend,walk,6,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap1-dym1p0-wo0,sidewall,descend,walk,6,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap1-dym2p0-wo0,sidewall,descend,walk,6,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap2-dy0p0-wo0,sidewall,flat,walk,6,2,0.0,,,0,True,3.579645798691743,1.2522033525119995,1.379645798691743, +sidewall-ascend-walk-mm6-gap2-dy1p0-wo0,sidewall,ascend,walk,6,2,1.0,,,0,True,2.8160046435139936,1.2522033525119995,0.6160046435139934, +sidewall-descend-walk-mm6-gap2-dym1p0-wo0,sidewall,descend,walk,6,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap2-dym2p0-wo0,sidewall,descend,walk,6,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap3-dy0p0-wo0,sidewall,flat,walk,6,3,0.0,,,0,True,3.579645798691743,1.2522033525119995,0.379645798691743, +sidewall-ascend-walk-mm6-gap3-dy1p0-wo0,sidewall,ascend,walk,6,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap3-dym1p0-wo0,sidewall,descend,walk,6,3,-1.0,,,0,True,4.051867994371985,1.2522033525119995,0.8518679943719851, +sidewall-descend-walk-mm6-gap3-dym2p0-wo0,sidewall,descend,walk,6,3,-2.0,,,0,True,4.426753110249335,1.2522033525119995,1.2267531102493345, +sidewall-flat-walk-mm6-gap4-dy0p0-wo0,sidewall,flat,walk,6,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap4-dy1p0-wo0,sidewall,ascend,walk,6,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap4-dym1p0-wo0,sidewall,descend,walk,6,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap4-dym2p0-wo0,sidewall,descend,walk,6,4,-2.0,,,0,True,4.426753110249335,1.2522033525119995,0.22675311024933453, +sidewall-flat-walk-mm6-gap5-dy0p0-wo0,sidewall,flat,walk,6,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap5-dy1p0-wo0,sidewall,ascend,walk,6,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap5-dym1p0-wo0,sidewall,descend,walk,6,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap5-dym2p0-wo0,sidewall,descend,walk,6,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap6-dy0p0-wo0,sidewall,flat,walk,6,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap6-dy1p0-wo0,sidewall,ascend,walk,6,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap6-dym1p0-wo0,sidewall,descend,walk,6,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap6-dym2p0-wo0,sidewall,descend,walk,6,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap7-dy0p0-wo0,sidewall,flat,walk,6,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap7-dy1p0-wo0,sidewall,ascend,walk,6,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap7-dym1p0-wo0,sidewall,descend,walk,6,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap7-dym2p0-wo0,sidewall,descend,walk,6,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap0-dy0p0-wo1,sidewall,flat,walk,6,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap0-dy1p0-wo1,sidewall,ascend,walk,6,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap0-dym1p0-wo1,sidewall,descend,walk,6,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap0-dym2p0-wo1,sidewall,descend,walk,6,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap1-dy0p0-wo1,sidewall,flat,walk,6,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap1-dy1p0-wo1,sidewall,ascend,walk,6,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap1-dym1p0-wo1,sidewall,descend,walk,6,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap1-dym2p0-wo1,sidewall,descend,walk,6,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap2-dy0p0-wo1,sidewall,flat,walk,6,2,0.0,,,1,True,3.579645798691743,1.2522033525119995,1.379645798691743, +sidewall-ascend-walk-mm6-gap2-dy1p0-wo1,sidewall,ascend,walk,6,2,1.0,,,1,True,2.8160046435139936,1.2522033525119995,0.6160046435139934, +sidewall-descend-walk-mm6-gap2-dym1p0-wo1,sidewall,descend,walk,6,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap2-dym2p0-wo1,sidewall,descend,walk,6,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap3-dy0p0-wo1,sidewall,flat,walk,6,3,0.0,,,1,True,3.579645798691743,1.2522033525119995,0.379645798691743, +sidewall-ascend-walk-mm6-gap3-dy1p0-wo1,sidewall,ascend,walk,6,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap3-dym1p0-wo1,sidewall,descend,walk,6,3,-1.0,,,1,True,4.051867994371985,1.2522033525119995,0.8518679943719851, +sidewall-descend-walk-mm6-gap3-dym2p0-wo1,sidewall,descend,walk,6,3,-2.0,,,1,True,4.426753110249335,1.2522033525119995,1.2267531102493345, +sidewall-flat-walk-mm6-gap4-dy0p0-wo1,sidewall,flat,walk,6,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap4-dy1p0-wo1,sidewall,ascend,walk,6,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap4-dym1p0-wo1,sidewall,descend,walk,6,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap4-dym2p0-wo1,sidewall,descend,walk,6,4,-2.0,,,1,True,4.426753110249335,1.2522033525119995,0.22675311024933453, +sidewall-flat-walk-mm6-gap5-dy0p0-wo1,sidewall,flat,walk,6,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap5-dy1p0-wo1,sidewall,ascend,walk,6,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap5-dym1p0-wo1,sidewall,descend,walk,6,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap5-dym2p0-wo1,sidewall,descend,walk,6,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap6-dy0p0-wo1,sidewall,flat,walk,6,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap6-dy1p0-wo1,sidewall,ascend,walk,6,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap6-dym1p0-wo1,sidewall,descend,walk,6,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap6-dym2p0-wo1,sidewall,descend,walk,6,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm6-gap7-dy0p0-wo1,sidewall,flat,walk,6,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm6-gap7-dy1p0-wo1,sidewall,ascend,walk,6,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap7-dym1p0-wo1,sidewall,descend,walk,6,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm6-gap7-dym2p0-wo1,sidewall,descend,walk,6,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap0-dy0p0-wo0,sidewall,flat,walk,7,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap0-dy1p0-wo0,sidewall,ascend,walk,7,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap0-dym1p0-wo0,sidewall,descend,walk,7,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap0-dym2p0-wo0,sidewall,descend,walk,7,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap1-dy0p0-wo0,sidewall,flat,walk,7,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap1-dy1p0-wo0,sidewall,ascend,walk,7,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap1-dym1p0-wo0,sidewall,descend,walk,7,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap1-dym2p0-wo0,sidewall,descend,walk,7,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap2-dy0p0-wo0,sidewall,flat,walk,7,2,0.0,,,0,True,3.593115406554676,1.2522033525119995,1.393115406554676, +sidewall-ascend-walk-mm7-gap2-dy1p0-wo0,sidewall,ascend,walk,7,2,1.0,,,0,True,2.8270031657890233,1.2522033525119995,0.6270031657890232, +sidewall-descend-walk-mm7-gap2-dym1p0-wo0,sidewall,descend,walk,7,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap2-dym2p0-wo0,sidewall,descend,walk,7,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap3-dy0p0-wo0,sidewall,flat,walk,7,3,0.0,,,0,True,3.593115406554676,1.2522033525119995,0.39311540655467603, +sidewall-ascend-walk-mm7-gap3-dy1p0-wo0,sidewall,ascend,walk,7,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap3-dym1p0-wo0,sidewall,descend,walk,7,3,-1.0,,,0,True,4.066563905366422,1.2522033525119995,0.866563905366422, +sidewall-descend-walk-mm7-gap3-dym2p0-wo0,sidewall,descend,walk,7,3,-2.0,,,0,True,4.442283130787294,1.2522033525119995,1.2422831307872935, +sidewall-flat-walk-mm7-gap4-dy0p0-wo0,sidewall,flat,walk,7,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap4-dy1p0-wo0,sidewall,ascend,walk,7,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap4-dym1p0-wo0,sidewall,descend,walk,7,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap4-dym2p0-wo0,sidewall,descend,walk,7,4,-2.0,,,0,True,4.442283130787294,1.2522033525119995,0.24228313078729347, +sidewall-flat-walk-mm7-gap5-dy0p0-wo0,sidewall,flat,walk,7,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap5-dy1p0-wo0,sidewall,ascend,walk,7,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap5-dym1p0-wo0,sidewall,descend,walk,7,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap5-dym2p0-wo0,sidewall,descend,walk,7,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap6-dy0p0-wo0,sidewall,flat,walk,7,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap6-dy1p0-wo0,sidewall,ascend,walk,7,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap6-dym1p0-wo0,sidewall,descend,walk,7,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap6-dym2p0-wo0,sidewall,descend,walk,7,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap7-dy0p0-wo0,sidewall,flat,walk,7,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap7-dy1p0-wo0,sidewall,ascend,walk,7,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap7-dym1p0-wo0,sidewall,descend,walk,7,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap7-dym2p0-wo0,sidewall,descend,walk,7,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap0-dy0p0-wo1,sidewall,flat,walk,7,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap0-dy1p0-wo1,sidewall,ascend,walk,7,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap0-dym1p0-wo1,sidewall,descend,walk,7,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap0-dym2p0-wo1,sidewall,descend,walk,7,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap1-dy0p0-wo1,sidewall,flat,walk,7,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap1-dy1p0-wo1,sidewall,ascend,walk,7,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap1-dym1p0-wo1,sidewall,descend,walk,7,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap1-dym2p0-wo1,sidewall,descend,walk,7,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap2-dy0p0-wo1,sidewall,flat,walk,7,2,0.0,,,1,True,3.593115406554676,1.2522033525119995,1.393115406554676, +sidewall-ascend-walk-mm7-gap2-dy1p0-wo1,sidewall,ascend,walk,7,2,1.0,,,1,True,2.8270031657890233,1.2522033525119995,0.6270031657890232, +sidewall-descend-walk-mm7-gap2-dym1p0-wo1,sidewall,descend,walk,7,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap2-dym2p0-wo1,sidewall,descend,walk,7,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap3-dy0p0-wo1,sidewall,flat,walk,7,3,0.0,,,1,True,3.593115406554676,1.2522033525119995,0.39311540655467603, +sidewall-ascend-walk-mm7-gap3-dy1p0-wo1,sidewall,ascend,walk,7,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap3-dym1p0-wo1,sidewall,descend,walk,7,3,-1.0,,,1,True,4.066563905366422,1.2522033525119995,0.866563905366422, +sidewall-descend-walk-mm7-gap3-dym2p0-wo1,sidewall,descend,walk,7,3,-2.0,,,1,True,4.442283130787294,1.2522033525119995,1.2422831307872935, +sidewall-flat-walk-mm7-gap4-dy0p0-wo1,sidewall,flat,walk,7,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap4-dy1p0-wo1,sidewall,ascend,walk,7,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap4-dym1p0-wo1,sidewall,descend,walk,7,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap4-dym2p0-wo1,sidewall,descend,walk,7,4,-2.0,,,1,True,4.442283130787294,1.2522033525119995,0.24228313078729347, +sidewall-flat-walk-mm7-gap5-dy0p0-wo1,sidewall,flat,walk,7,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap5-dy1p0-wo1,sidewall,ascend,walk,7,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap5-dym1p0-wo1,sidewall,descend,walk,7,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap5-dym2p0-wo1,sidewall,descend,walk,7,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap6-dy0p0-wo1,sidewall,flat,walk,7,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap6-dy1p0-wo1,sidewall,ascend,walk,7,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap6-dym1p0-wo1,sidewall,descend,walk,7,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap6-dym2p0-wo1,sidewall,descend,walk,7,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm7-gap7-dy0p0-wo1,sidewall,flat,walk,7,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm7-gap7-dy1p0-wo1,sidewall,ascend,walk,7,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap7-dym1p0-wo1,sidewall,descend,walk,7,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm7-gap7-dym2p0-wo1,sidewall,descend,walk,7,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap0-dy0p0-wo0,sidewall,flat,walk,8,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap0-dy1p0-wo0,sidewall,ascend,walk,8,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap0-dym1p0-wo0,sidewall,descend,walk,8,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap0-dym2p0-wo0,sidewall,descend,walk,8,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap1-dy0p0-wo0,sidewall,flat,walk,8,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap1-dy1p0-wo0,sidewall,ascend,walk,8,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap1-dym1p0-wo0,sidewall,descend,walk,8,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap1-dym2p0-wo0,sidewall,descend,walk,8,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap2-dy0p0-wo0,sidewall,flat,walk,8,2,0.0,,,0,True,3.600469812447837,1.2522033525119995,1.4004698124478367, +sidewall-ascend-walk-mm8-gap2-dy1p0-wo0,sidewall,ascend,walk,8,2,1.0,,,0,True,2.833008358951189,1.2522033525119995,0.633008358951189, +sidewall-descend-walk-mm8-gap2-dym1p0-wo0,sidewall,descend,walk,8,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap2-dym2p0-wo0,sidewall,descend,walk,8,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap3-dy0p0-wo0,sidewall,flat,walk,8,3,0.0,,,0,True,3.600469812447837,1.2522033525119995,0.40046981244783675, +sidewall-ascend-walk-mm8-gap3-dy1p0-wo0,sidewall,ascend,walk,8,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap3-dym1p0-wo0,sidewall,descend,walk,8,3,-1.0,,,0,True,4.074587872769384,1.2522033525119995,0.8745878727693839, +sidewall-descend-walk-mm8-gap3-dym2p0-wo0,sidewall,descend,walk,8,3,-2.0,,,0,True,4.4507625220010185,1.2522033525119995,1.2507625220010183, +sidewall-flat-walk-mm8-gap4-dy0p0-wo0,sidewall,flat,walk,8,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap4-dy1p0-wo0,sidewall,ascend,walk,8,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap4-dym1p0-wo0,sidewall,descend,walk,8,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap4-dym2p0-wo0,sidewall,descend,walk,8,4,-2.0,,,0,True,4.4507625220010185,1.2522033525119995,0.25076252200101834, +sidewall-flat-walk-mm8-gap5-dy0p0-wo0,sidewall,flat,walk,8,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap5-dy1p0-wo0,sidewall,ascend,walk,8,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap5-dym1p0-wo0,sidewall,descend,walk,8,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap5-dym2p0-wo0,sidewall,descend,walk,8,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap6-dy0p0-wo0,sidewall,flat,walk,8,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap6-dy1p0-wo0,sidewall,ascend,walk,8,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap6-dym1p0-wo0,sidewall,descend,walk,8,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap6-dym2p0-wo0,sidewall,descend,walk,8,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap7-dy0p0-wo0,sidewall,flat,walk,8,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap7-dy1p0-wo0,sidewall,ascend,walk,8,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap7-dym1p0-wo0,sidewall,descend,walk,8,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap7-dym2p0-wo0,sidewall,descend,walk,8,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap0-dy0p0-wo1,sidewall,flat,walk,8,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap0-dy1p0-wo1,sidewall,ascend,walk,8,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap0-dym1p0-wo1,sidewall,descend,walk,8,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap0-dym2p0-wo1,sidewall,descend,walk,8,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap1-dy0p0-wo1,sidewall,flat,walk,8,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap1-dy1p0-wo1,sidewall,ascend,walk,8,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap1-dym1p0-wo1,sidewall,descend,walk,8,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap1-dym2p0-wo1,sidewall,descend,walk,8,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap2-dy0p0-wo1,sidewall,flat,walk,8,2,0.0,,,1,True,3.600469812447837,1.2522033525119995,1.4004698124478367, +sidewall-ascend-walk-mm8-gap2-dy1p0-wo1,sidewall,ascend,walk,8,2,1.0,,,1,True,2.833008358951189,1.2522033525119995,0.633008358951189, +sidewall-descend-walk-mm8-gap2-dym1p0-wo1,sidewall,descend,walk,8,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap2-dym2p0-wo1,sidewall,descend,walk,8,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap3-dy0p0-wo1,sidewall,flat,walk,8,3,0.0,,,1,True,3.600469812447837,1.2522033525119995,0.40046981244783675, +sidewall-ascend-walk-mm8-gap3-dy1p0-wo1,sidewall,ascend,walk,8,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap3-dym1p0-wo1,sidewall,descend,walk,8,3,-1.0,,,1,True,4.074587872769384,1.2522033525119995,0.8745878727693839, +sidewall-descend-walk-mm8-gap3-dym2p0-wo1,sidewall,descend,walk,8,3,-2.0,,,1,True,4.4507625220010185,1.2522033525119995,1.2507625220010183, +sidewall-flat-walk-mm8-gap4-dy0p0-wo1,sidewall,flat,walk,8,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap4-dy1p0-wo1,sidewall,ascend,walk,8,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap4-dym1p0-wo1,sidewall,descend,walk,8,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap4-dym2p0-wo1,sidewall,descend,walk,8,4,-2.0,,,1,True,4.4507625220010185,1.2522033525119995,0.25076252200101834, +sidewall-flat-walk-mm8-gap5-dy0p0-wo1,sidewall,flat,walk,8,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap5-dy1p0-wo1,sidewall,ascend,walk,8,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap5-dym1p0-wo1,sidewall,descend,walk,8,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap5-dym2p0-wo1,sidewall,descend,walk,8,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap6-dy0p0-wo1,sidewall,flat,walk,8,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap6-dy1p0-wo1,sidewall,ascend,walk,8,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap6-dym1p0-wo1,sidewall,descend,walk,8,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap6-dym2p0-wo1,sidewall,descend,walk,8,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm8-gap7-dy0p0-wo1,sidewall,flat,walk,8,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm8-gap7-dy1p0-wo1,sidewall,ascend,walk,8,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap7-dym1p0-wo1,sidewall,descend,walk,8,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm8-gap7-dym2p0-wo1,sidewall,descend,walk,8,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap0-dy0p0-wo0,sidewall,flat,walk,9,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap0-dy1p0-wo0,sidewall,ascend,walk,9,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap0-dym1p0-wo0,sidewall,descend,walk,9,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap0-dym2p0-wo0,sidewall,descend,walk,9,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap1-dy0p0-wo0,sidewall,flat,walk,9,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap1-dy1p0-wo0,sidewall,ascend,walk,9,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap1-dym1p0-wo0,sidewall,descend,walk,9,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap1-dym2p0-wo0,sidewall,descend,walk,9,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap2-dy0p0-wo0,sidewall,flat,walk,9,2,0.0,,,0,True,3.604485318065503,1.2522033525119995,1.404485318065503, +sidewall-ascend-walk-mm9-gap2-dy1p0-wo0,sidewall,ascend,walk,9,2,1.0,,,0,True,2.836287194417732,1.2522033525119995,0.636287194417732, +sidewall-descend-walk-mm9-gap2-dym1p0-wo0,sidewall,descend,walk,9,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap2-dym2p0-wo0,sidewall,descend,walk,9,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap3-dy0p0-wo0,sidewall,flat,walk,9,3,0.0,,,0,True,3.604485318065503,1.2522033525119995,0.40448531806550303, +sidewall-ascend-walk-mm9-gap3-dy1p0-wo0,sidewall,ascend,walk,9,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap3-dym1p0-wo0,sidewall,descend,walk,9,3,-1.0,,,0,True,4.078968958971402,1.2522033525119995,0.8789689589714023, +sidewall-descend-walk-mm9-gap3-dym2p0-wo0,sidewall,descend,walk,9,3,-2.0,,,0,True,4.455392269603713,1.2522033525119995,1.2553922696037132, +sidewall-flat-walk-mm9-gap4-dy0p0-wo0,sidewall,flat,walk,9,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap4-dy1p0-wo0,sidewall,ascend,walk,9,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap4-dym1p0-wo0,sidewall,descend,walk,9,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap4-dym2p0-wo0,sidewall,descend,walk,9,4,-2.0,,,0,True,4.455392269603713,1.2522033525119995,0.2553922696037132, +sidewall-flat-walk-mm9-gap5-dy0p0-wo0,sidewall,flat,walk,9,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap5-dy1p0-wo0,sidewall,ascend,walk,9,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap5-dym1p0-wo0,sidewall,descend,walk,9,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap5-dym2p0-wo0,sidewall,descend,walk,9,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap6-dy0p0-wo0,sidewall,flat,walk,9,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap6-dy1p0-wo0,sidewall,ascend,walk,9,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap6-dym1p0-wo0,sidewall,descend,walk,9,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap6-dym2p0-wo0,sidewall,descend,walk,9,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap7-dy0p0-wo0,sidewall,flat,walk,9,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap7-dy1p0-wo0,sidewall,ascend,walk,9,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap7-dym1p0-wo0,sidewall,descend,walk,9,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap7-dym2p0-wo0,sidewall,descend,walk,9,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap0-dy0p0-wo1,sidewall,flat,walk,9,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap0-dy1p0-wo1,sidewall,ascend,walk,9,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap0-dym1p0-wo1,sidewall,descend,walk,9,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap0-dym2p0-wo1,sidewall,descend,walk,9,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap1-dy0p0-wo1,sidewall,flat,walk,9,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap1-dy1p0-wo1,sidewall,ascend,walk,9,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap1-dym1p0-wo1,sidewall,descend,walk,9,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap1-dym2p0-wo1,sidewall,descend,walk,9,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap2-dy0p0-wo1,sidewall,flat,walk,9,2,0.0,,,1,True,3.604485318065503,1.2522033525119995,1.404485318065503, +sidewall-ascend-walk-mm9-gap2-dy1p0-wo1,sidewall,ascend,walk,9,2,1.0,,,1,True,2.836287194417732,1.2522033525119995,0.636287194417732, +sidewall-descend-walk-mm9-gap2-dym1p0-wo1,sidewall,descend,walk,9,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap2-dym2p0-wo1,sidewall,descend,walk,9,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap3-dy0p0-wo1,sidewall,flat,walk,9,3,0.0,,,1,True,3.604485318065503,1.2522033525119995,0.40448531806550303, +sidewall-ascend-walk-mm9-gap3-dy1p0-wo1,sidewall,ascend,walk,9,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap3-dym1p0-wo1,sidewall,descend,walk,9,3,-1.0,,,1,True,4.078968958971402,1.2522033525119995,0.8789689589714023, +sidewall-descend-walk-mm9-gap3-dym2p0-wo1,sidewall,descend,walk,9,3,-2.0,,,1,True,4.455392269603713,1.2522033525119995,1.2553922696037132, +sidewall-flat-walk-mm9-gap4-dy0p0-wo1,sidewall,flat,walk,9,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap4-dy1p0-wo1,sidewall,ascend,walk,9,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap4-dym1p0-wo1,sidewall,descend,walk,9,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap4-dym2p0-wo1,sidewall,descend,walk,9,4,-2.0,,,1,True,4.455392269603713,1.2522033525119995,0.2553922696037132, +sidewall-flat-walk-mm9-gap5-dy0p0-wo1,sidewall,flat,walk,9,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap5-dy1p0-wo1,sidewall,ascend,walk,9,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap5-dym1p0-wo1,sidewall,descend,walk,9,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap5-dym2p0-wo1,sidewall,descend,walk,9,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap6-dy0p0-wo1,sidewall,flat,walk,9,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap6-dy1p0-wo1,sidewall,ascend,walk,9,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap6-dym1p0-wo1,sidewall,descend,walk,9,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap6-dym2p0-wo1,sidewall,descend,walk,9,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm9-gap7-dy0p0-wo1,sidewall,flat,walk,9,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm9-gap7-dy1p0-wo1,sidewall,ascend,walk,9,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap7-dym1p0-wo1,sidewall,descend,walk,9,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm9-gap7-dym2p0-wo1,sidewall,descend,walk,9,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap0-dy0p0-wo0,sidewall,flat,walk,10,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap0-dy1p0-wo0,sidewall,ascend,walk,10,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap0-dym1p0-wo0,sidewall,descend,walk,10,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap0-dym2p0-wo0,sidewall,descend,walk,10,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap1-dy0p0-wo0,sidewall,flat,walk,10,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap1-dy1p0-wo0,sidewall,ascend,walk,10,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap1-dym1p0-wo0,sidewall,descend,walk,10,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap1-dym2p0-wo0,sidewall,descend,walk,10,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap2-dy0p0-wo0,sidewall,flat,walk,10,2,0.0,,,0,True,3.6066777841327484,1.2522033525119995,1.4066777841327482, +sidewall-ascend-walk-mm10-gap2-dy1p0-wo0,sidewall,ascend,walk,10,2,1.0,,,0,True,2.8380774385824643,1.2522033525119995,0.6380774385824641, +sidewall-descend-walk-mm10-gap2-dym1p0-wo0,sidewall,descend,walk,10,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap2-dym2p0-wo0,sidewall,descend,walk,10,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap3-dy0p0-wo0,sidewall,flat,walk,10,3,0.0,,,0,True,3.6066777841327484,1.2522033525119995,0.40667778413274824, +sidewall-ascend-walk-mm10-gap3-dy1p0-wo0,sidewall,ascend,walk,10,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap3-dym1p0-wo0,sidewall,descend,walk,10,3,-1.0,,,0,True,4.081361032037703,1.2522033525119995,0.8813610320377032, +sidewall-descend-walk-mm10-gap3-dym2p0-wo0,sidewall,descend,walk,10,3,-2.0,,,0,True,4.457920111794784,1.2522033525119995,1.2579201117947836, +sidewall-flat-walk-mm10-gap4-dy0p0-wo0,sidewall,flat,walk,10,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap4-dy1p0-wo0,sidewall,ascend,walk,10,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap4-dym1p0-wo0,sidewall,descend,walk,10,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap4-dym2p0-wo0,sidewall,descend,walk,10,4,-2.0,,,0,True,4.457920111794784,1.2522033525119995,0.2579201117947836, +sidewall-flat-walk-mm10-gap5-dy0p0-wo0,sidewall,flat,walk,10,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap5-dy1p0-wo0,sidewall,ascend,walk,10,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap5-dym1p0-wo0,sidewall,descend,walk,10,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap5-dym2p0-wo0,sidewall,descend,walk,10,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap6-dy0p0-wo0,sidewall,flat,walk,10,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap6-dy1p0-wo0,sidewall,ascend,walk,10,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap6-dym1p0-wo0,sidewall,descend,walk,10,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap6-dym2p0-wo0,sidewall,descend,walk,10,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap7-dy0p0-wo0,sidewall,flat,walk,10,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap7-dy1p0-wo0,sidewall,ascend,walk,10,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap7-dym1p0-wo0,sidewall,descend,walk,10,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap7-dym2p0-wo0,sidewall,descend,walk,10,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap0-dy0p0-wo1,sidewall,flat,walk,10,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap0-dy1p0-wo1,sidewall,ascend,walk,10,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap0-dym1p0-wo1,sidewall,descend,walk,10,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap0-dym2p0-wo1,sidewall,descend,walk,10,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap1-dy0p0-wo1,sidewall,flat,walk,10,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap1-dy1p0-wo1,sidewall,ascend,walk,10,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap1-dym1p0-wo1,sidewall,descend,walk,10,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap1-dym2p0-wo1,sidewall,descend,walk,10,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap2-dy0p0-wo1,sidewall,flat,walk,10,2,0.0,,,1,True,3.6066777841327484,1.2522033525119995,1.4066777841327482, +sidewall-ascend-walk-mm10-gap2-dy1p0-wo1,sidewall,ascend,walk,10,2,1.0,,,1,True,2.8380774385824643,1.2522033525119995,0.6380774385824641, +sidewall-descend-walk-mm10-gap2-dym1p0-wo1,sidewall,descend,walk,10,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap2-dym2p0-wo1,sidewall,descend,walk,10,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap3-dy0p0-wo1,sidewall,flat,walk,10,3,0.0,,,1,True,3.6066777841327484,1.2522033525119995,0.40667778413274824, +sidewall-ascend-walk-mm10-gap3-dy1p0-wo1,sidewall,ascend,walk,10,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap3-dym1p0-wo1,sidewall,descend,walk,10,3,-1.0,,,1,True,4.081361032037703,1.2522033525119995,0.8813610320377032, +sidewall-descend-walk-mm10-gap3-dym2p0-wo1,sidewall,descend,walk,10,3,-2.0,,,1,True,4.457920111794784,1.2522033525119995,1.2579201117947836, +sidewall-flat-walk-mm10-gap4-dy0p0-wo1,sidewall,flat,walk,10,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap4-dy1p0-wo1,sidewall,ascend,walk,10,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap4-dym1p0-wo1,sidewall,descend,walk,10,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap4-dym2p0-wo1,sidewall,descend,walk,10,4,-2.0,,,1,True,4.457920111794784,1.2522033525119995,0.2579201117947836, +sidewall-flat-walk-mm10-gap5-dy0p0-wo1,sidewall,flat,walk,10,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap5-dy1p0-wo1,sidewall,ascend,walk,10,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap5-dym1p0-wo1,sidewall,descend,walk,10,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap5-dym2p0-wo1,sidewall,descend,walk,10,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap6-dy0p0-wo1,sidewall,flat,walk,10,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap6-dy1p0-wo1,sidewall,ascend,walk,10,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap6-dym1p0-wo1,sidewall,descend,walk,10,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap6-dym2p0-wo1,sidewall,descend,walk,10,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm10-gap7-dy0p0-wo1,sidewall,flat,walk,10,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm10-gap7-dy1p0-wo1,sidewall,ascend,walk,10,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap7-dym1p0-wo1,sidewall,descend,walk,10,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm10-gap7-dym2p0-wo1,sidewall,descend,walk,10,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap0-dy0p0-wo0,sidewall,flat,walk,11,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap0-dy1p0-wo0,sidewall,ascend,walk,11,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap0-dym1p0-wo0,sidewall,descend,walk,11,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap0-dym2p0-wo0,sidewall,descend,walk,11,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap1-dy0p0-wo0,sidewall,flat,walk,11,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap1-dy1p0-wo0,sidewall,ascend,walk,11,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap1-dym1p0-wo0,sidewall,descend,walk,11,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap1-dym2p0-wo0,sidewall,descend,walk,11,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap2-dy0p0-wo0,sidewall,flat,walk,11,2,0.0,,,0,True,3.607874870605464,1.2522033525119995,1.4078748706054638, +sidewall-ascend-walk-mm11-gap2-dy1p0-wo0,sidewall,ascend,walk,11,2,1.0,,,0,True,2.8390549118964077,1.2522033525119995,0.6390549118964075, +sidewall-descend-walk-mm11-gap2-dym1p0-wo0,sidewall,descend,walk,11,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap2-dym2p0-wo0,sidewall,descend,walk,11,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap3-dy0p0-wo0,sidewall,flat,walk,11,3,0.0,,,0,True,3.607874870605464,1.2522033525119995,0.4078748706054638, +sidewall-ascend-walk-mm11-gap3-dy1p0-wo0,sidewall,ascend,walk,11,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap3-dym1p0-wo0,sidewall,descend,walk,11,3,-1.0,,,0,True,4.082667103931904,1.2522033525119995,0.8826671039319036, +sidewall-descend-walk-mm11-gap3-dym2p0-wo0,sidewall,descend,walk,11,3,-2.0,,,0,True,4.459300313631108,1.2522033525119995,1.2593003136311074, +sidewall-flat-walk-mm11-gap4-dy0p0-wo0,sidewall,flat,walk,11,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap4-dy1p0-wo0,sidewall,ascend,walk,11,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap4-dym1p0-wo0,sidewall,descend,walk,11,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap4-dym2p0-wo0,sidewall,descend,walk,11,4,-2.0,,,0,True,4.459300313631108,1.2522033525119995,0.2593003136311074, +sidewall-flat-walk-mm11-gap5-dy0p0-wo0,sidewall,flat,walk,11,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap5-dy1p0-wo0,sidewall,ascend,walk,11,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap5-dym1p0-wo0,sidewall,descend,walk,11,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap5-dym2p0-wo0,sidewall,descend,walk,11,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap6-dy0p0-wo0,sidewall,flat,walk,11,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap6-dy1p0-wo0,sidewall,ascend,walk,11,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap6-dym1p0-wo0,sidewall,descend,walk,11,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap6-dym2p0-wo0,sidewall,descend,walk,11,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap7-dy0p0-wo0,sidewall,flat,walk,11,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap7-dy1p0-wo0,sidewall,ascend,walk,11,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap7-dym1p0-wo0,sidewall,descend,walk,11,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap7-dym2p0-wo0,sidewall,descend,walk,11,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap0-dy0p0-wo1,sidewall,flat,walk,11,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap0-dy1p0-wo1,sidewall,ascend,walk,11,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap0-dym1p0-wo1,sidewall,descend,walk,11,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap0-dym2p0-wo1,sidewall,descend,walk,11,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap1-dy0p0-wo1,sidewall,flat,walk,11,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap1-dy1p0-wo1,sidewall,ascend,walk,11,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap1-dym1p0-wo1,sidewall,descend,walk,11,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap1-dym2p0-wo1,sidewall,descend,walk,11,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap2-dy0p0-wo1,sidewall,flat,walk,11,2,0.0,,,1,True,3.607874870605464,1.2522033525119995,1.4078748706054638, +sidewall-ascend-walk-mm11-gap2-dy1p0-wo1,sidewall,ascend,walk,11,2,1.0,,,1,True,2.8390549118964077,1.2522033525119995,0.6390549118964075, +sidewall-descend-walk-mm11-gap2-dym1p0-wo1,sidewall,descend,walk,11,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap2-dym2p0-wo1,sidewall,descend,walk,11,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap3-dy0p0-wo1,sidewall,flat,walk,11,3,0.0,,,1,True,3.607874870605464,1.2522033525119995,0.4078748706054638, +sidewall-ascend-walk-mm11-gap3-dy1p0-wo1,sidewall,ascend,walk,11,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap3-dym1p0-wo1,sidewall,descend,walk,11,3,-1.0,,,1,True,4.082667103931904,1.2522033525119995,0.8826671039319036, +sidewall-descend-walk-mm11-gap3-dym2p0-wo1,sidewall,descend,walk,11,3,-2.0,,,1,True,4.459300313631108,1.2522033525119995,1.2593003136311074, +sidewall-flat-walk-mm11-gap4-dy0p0-wo1,sidewall,flat,walk,11,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap4-dy1p0-wo1,sidewall,ascend,walk,11,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap4-dym1p0-wo1,sidewall,descend,walk,11,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap4-dym2p0-wo1,sidewall,descend,walk,11,4,-2.0,,,1,True,4.459300313631108,1.2522033525119995,0.2593003136311074, +sidewall-flat-walk-mm11-gap5-dy0p0-wo1,sidewall,flat,walk,11,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap5-dy1p0-wo1,sidewall,ascend,walk,11,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap5-dym1p0-wo1,sidewall,descend,walk,11,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap5-dym2p0-wo1,sidewall,descend,walk,11,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap6-dy0p0-wo1,sidewall,flat,walk,11,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap6-dy1p0-wo1,sidewall,ascend,walk,11,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap6-dym1p0-wo1,sidewall,descend,walk,11,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap6-dym2p0-wo1,sidewall,descend,walk,11,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm11-gap7-dy0p0-wo1,sidewall,flat,walk,11,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm11-gap7-dy1p0-wo1,sidewall,ascend,walk,11,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap7-dym1p0-wo1,sidewall,descend,walk,11,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm11-gap7-dym2p0-wo1,sidewall,descend,walk,11,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap0-dy0p0-wo0,sidewall,flat,walk,12,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap0-dy1p0-wo0,sidewall,ascend,walk,12,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap0-dym1p0-wo0,sidewall,descend,walk,12,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap0-dym2p0-wo0,sidewall,descend,walk,12,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap1-dy0p0-wo0,sidewall,flat,walk,12,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap1-dy1p0-wo0,sidewall,ascend,walk,12,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap1-dym1p0-wo0,sidewall,descend,walk,12,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap1-dym2p0-wo0,sidewall,descend,walk,12,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap2-dy0p0-wo0,sidewall,flat,walk,12,2,0.0,,,0,True,3.608528479819567,1.2522033525119995,1.4085284798195667, +sidewall-ascend-walk-mm12-gap2-dy1p0-wo0,sidewall,ascend,walk,12,2,1.0,,,0,True,2.839588612325821,1.2522033525119995,0.639588612325821, +sidewall-descend-walk-mm12-gap2-dym1p0-wo0,sidewall,descend,walk,12,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap2-dym2p0-wo0,sidewall,descend,walk,12,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap3-dy0p0-wo0,sidewall,flat,walk,12,3,0.0,,,0,True,3.608528479819567,1.2522033525119995,0.40852847981956675, +sidewall-ascend-walk-mm12-gap3-dy1p0-wo0,sidewall,ascend,walk,12,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap3-dym1p0-wo0,sidewall,descend,walk,12,3,-1.0,,,0,True,4.083380219186137,1.2522033525119995,0.8833802191861366, +sidewall-descend-walk-mm12-gap3-dym2p0-wo0,sidewall,descend,walk,12,3,-2.0,,,0,True,4.4600539038337415,1.2522033525119995,1.2600539038337413, +sidewall-flat-walk-mm12-gap4-dy0p0-wo0,sidewall,flat,walk,12,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap4-dy1p0-wo0,sidewall,ascend,walk,12,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap4-dym1p0-wo0,sidewall,descend,walk,12,4,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap4-dym2p0-wo0,sidewall,descend,walk,12,4,-2.0,,,0,True,4.4600539038337415,1.2522033525119995,0.2600539038337413, +sidewall-flat-walk-mm12-gap5-dy0p0-wo0,sidewall,flat,walk,12,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap5-dy1p0-wo0,sidewall,ascend,walk,12,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap5-dym1p0-wo0,sidewall,descend,walk,12,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap5-dym2p0-wo0,sidewall,descend,walk,12,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap6-dy0p0-wo0,sidewall,flat,walk,12,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap6-dy1p0-wo0,sidewall,ascend,walk,12,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap6-dym1p0-wo0,sidewall,descend,walk,12,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap6-dym2p0-wo0,sidewall,descend,walk,12,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap7-dy0p0-wo0,sidewall,flat,walk,12,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap7-dy1p0-wo0,sidewall,ascend,walk,12,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap7-dym1p0-wo0,sidewall,descend,walk,12,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap7-dym2p0-wo0,sidewall,descend,walk,12,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap0-dy0p0-wo1,sidewall,flat,walk,12,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap0-dy1p0-wo1,sidewall,ascend,walk,12,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap0-dym1p0-wo1,sidewall,descend,walk,12,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap0-dym2p0-wo1,sidewall,descend,walk,12,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap1-dy0p0-wo1,sidewall,flat,walk,12,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap1-dy1p0-wo1,sidewall,ascend,walk,12,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap1-dym1p0-wo1,sidewall,descend,walk,12,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap1-dym2p0-wo1,sidewall,descend,walk,12,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap2-dy0p0-wo1,sidewall,flat,walk,12,2,0.0,,,1,True,3.608528479819567,1.2522033525119995,1.4085284798195667, +sidewall-ascend-walk-mm12-gap2-dy1p0-wo1,sidewall,ascend,walk,12,2,1.0,,,1,True,2.839588612325821,1.2522033525119995,0.639588612325821, +sidewall-descend-walk-mm12-gap2-dym1p0-wo1,sidewall,descend,walk,12,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap2-dym2p0-wo1,sidewall,descend,walk,12,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap3-dy0p0-wo1,sidewall,flat,walk,12,3,0.0,,,1,True,3.608528479819567,1.2522033525119995,0.40852847981956675, +sidewall-ascend-walk-mm12-gap3-dy1p0-wo1,sidewall,ascend,walk,12,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap3-dym1p0-wo1,sidewall,descend,walk,12,3,-1.0,,,1,True,4.083380219186137,1.2522033525119995,0.8833802191861366, +sidewall-descend-walk-mm12-gap3-dym2p0-wo1,sidewall,descend,walk,12,3,-2.0,,,1,True,4.4600539038337415,1.2522033525119995,1.2600539038337413, +sidewall-flat-walk-mm12-gap4-dy0p0-wo1,sidewall,flat,walk,12,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap4-dy1p0-wo1,sidewall,ascend,walk,12,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap4-dym1p0-wo1,sidewall,descend,walk,12,4,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap4-dym2p0-wo1,sidewall,descend,walk,12,4,-2.0,,,1,True,4.4600539038337415,1.2522033525119995,0.2600539038337413, +sidewall-flat-walk-mm12-gap5-dy0p0-wo1,sidewall,flat,walk,12,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap5-dy1p0-wo1,sidewall,ascend,walk,12,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap5-dym1p0-wo1,sidewall,descend,walk,12,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap5-dym2p0-wo1,sidewall,descend,walk,12,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap6-dy0p0-wo1,sidewall,flat,walk,12,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap6-dy1p0-wo1,sidewall,ascend,walk,12,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap6-dym1p0-wo1,sidewall,descend,walk,12,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap6-dym2p0-wo1,sidewall,descend,walk,12,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-walk-mm12-gap7-dy0p0-wo1,sidewall,flat,walk,12,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-walk-mm12-gap7-dy1p0-wo1,sidewall,ascend,walk,12,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap7-dym1p0-wo1,sidewall,descend,walk,12,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-walk-mm12-gap7-dym2p0-wo1,sidewall,descend,walk,12,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap0-dy0p0-wo0,sidewall,flat,sprint,0,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap0-dy1p0-wo0,sidewall,ascend,sprint,0,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap0-dym1p0-wo0,sidewall,descend,sprint,0,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap0-dym2p0-wo0,sidewall,descend,sprint,0,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap1-dy0p0-wo0,sidewall,flat,sprint,0,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap1-dy1p0-wo0,sidewall,ascend,sprint,0,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap1-dym1p0-wo0,sidewall,descend,sprint,0,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap1-dym2p0-wo0,sidewall,descend,sprint,0,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap2-dy0p0-wo0,sidewall,flat,sprint,0,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap2-dy1p0-wo0,sidewall,ascend,sprint,0,2,1.0,,,0,True,3.0951266201794545,1.2522033525119995,0.8951266201794543, +sidewall-descend-sprint-mm0-gap2-dym1p0-wo0,sidewall,descend,sprint,0,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap2-dym2p0-wo0,sidewall,descend,sprint,0,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap3-dy0p0-wo0,sidewall,flat,sprint,0,3,0.0,,,0,True,3.9214793175587332,1.2522033525119995,0.7214793175587331, +sidewall-ascend-sprint-mm0-gap3-dy1p0-wo0,sidewall,ascend,sprint,0,3,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap3-dym1p0-wo0,sidewall,descend,sprint,0,3,-1.0,,,0,True,4.424822798944206,1.2522033525119995,1.2248227989442055, +sidewall-descend-sprint-mm0-gap3-dym2p0-wo0,sidewall,descend,sprint,0,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap4-dy0p0-wo0,sidewall,flat,sprint,0,4,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap4-dy1p0-wo0,sidewall,ascend,sprint,0,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap4-dym1p0-wo0,sidewall,descend,sprint,0,4,-1.0,,,0,True,4.424822798944206,1.2522033525119995,0.22482279894420554, +sidewall-descend-sprint-mm0-gap4-dym2p0-wo0,sidewall,descend,sprint,0,4,-2.0,,,0,True,4.820876058934151,1.2522033525119995,0.6208760589341509, +sidewall-flat-sprint-mm0-gap5-dy0p0-wo0,sidewall,flat,sprint,0,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap5-dy1p0-wo0,sidewall,ascend,sprint,0,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap5-dym1p0-wo0,sidewall,descend,sprint,0,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap5-dym2p0-wo0,sidewall,descend,sprint,0,5,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap6-dy0p0-wo0,sidewall,flat,sprint,0,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap6-dy1p0-wo0,sidewall,ascend,sprint,0,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap6-dym1p0-wo0,sidewall,descend,sprint,0,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap6-dym2p0-wo0,sidewall,descend,sprint,0,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap7-dy0p0-wo0,sidewall,flat,sprint,0,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap7-dy1p0-wo0,sidewall,ascend,sprint,0,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap7-dym1p0-wo0,sidewall,descend,sprint,0,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap7-dym2p0-wo0,sidewall,descend,sprint,0,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap0-dy0p0-wo1,sidewall,flat,sprint,0,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap0-dy1p0-wo1,sidewall,ascend,sprint,0,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap0-dym1p0-wo1,sidewall,descend,sprint,0,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap0-dym2p0-wo1,sidewall,descend,sprint,0,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap1-dy0p0-wo1,sidewall,flat,sprint,0,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap1-dy1p0-wo1,sidewall,ascend,sprint,0,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap1-dym1p0-wo1,sidewall,descend,sprint,0,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap1-dym2p0-wo1,sidewall,descend,sprint,0,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap2-dy0p0-wo1,sidewall,flat,sprint,0,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap2-dy1p0-wo1,sidewall,ascend,sprint,0,2,1.0,,,1,True,3.0951266201794545,1.2522033525119995,0.8951266201794543, +sidewall-descend-sprint-mm0-gap2-dym1p0-wo1,sidewall,descend,sprint,0,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap2-dym2p0-wo1,sidewall,descend,sprint,0,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap3-dy0p0-wo1,sidewall,flat,sprint,0,3,0.0,,,1,True,3.9214793175587332,1.2522033525119995,0.7214793175587331, +sidewall-ascend-sprint-mm0-gap3-dy1p0-wo1,sidewall,ascend,sprint,0,3,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap3-dym1p0-wo1,sidewall,descend,sprint,0,3,-1.0,,,1,True,4.424822798944206,1.2522033525119995,1.2248227989442055, +sidewall-descend-sprint-mm0-gap3-dym2p0-wo1,sidewall,descend,sprint,0,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap4-dy0p0-wo1,sidewall,flat,sprint,0,4,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap4-dy1p0-wo1,sidewall,ascend,sprint,0,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap4-dym1p0-wo1,sidewall,descend,sprint,0,4,-1.0,,,1,True,4.424822798944206,1.2522033525119995,0.22482279894420554, +sidewall-descend-sprint-mm0-gap4-dym2p0-wo1,sidewall,descend,sprint,0,4,-2.0,,,1,True,4.820876058934151,1.2522033525119995,0.6208760589341509, +sidewall-flat-sprint-mm0-gap5-dy0p0-wo1,sidewall,flat,sprint,0,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap5-dy1p0-wo1,sidewall,ascend,sprint,0,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap5-dym1p0-wo1,sidewall,descend,sprint,0,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap5-dym2p0-wo1,sidewall,descend,sprint,0,5,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap6-dy0p0-wo1,sidewall,flat,sprint,0,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap6-dy1p0-wo1,sidewall,ascend,sprint,0,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap6-dym1p0-wo1,sidewall,descend,sprint,0,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap6-dym2p0-wo1,sidewall,descend,sprint,0,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm0-gap7-dy0p0-wo1,sidewall,flat,sprint,0,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm0-gap7-dy1p0-wo1,sidewall,ascend,sprint,0,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap7-dym1p0-wo1,sidewall,descend,sprint,0,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm0-gap7-dym2p0-wo1,sidewall,descend,sprint,0,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap0-dy0p0-wo0,sidewall,flat,sprint,1,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap0-dy1p0-wo0,sidewall,ascend,sprint,1,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap0-dym1p0-wo0,sidewall,descend,sprint,1,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap0-dym2p0-wo0,sidewall,descend,sprint,1,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap1-dy0p0-wo0,sidewall,flat,sprint,1,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap1-dy1p0-wo0,sidewall,ascend,sprint,1,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap1-dym1p0-wo0,sidewall,descend,sprint,1,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap1-dym2p0-wo0,sidewall,descend,sprint,1,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap2-dy0p0-wo0,sidewall,flat,sprint,1,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap2-dy1p0-wo0,sidewall,ascend,sprint,1,2,1.0,,,0,True,3.5102512319825148,1.2522033525119995,1.3102512319825146, +sidewall-descend-sprint-mm1-gap2-dym1p0-wo0,sidewall,descend,sprint,1,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap2-dym2p0-wo0,sidewall,descend,sprint,1,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap3-dy0p0-wo0,sidewall,flat,sprint,1,3,0.0,,,0,True,4.4298717720554635,1.2522033525119995,1.2298717720554633, +sidewall-ascend-sprint-mm1-gap3-dy1p0-wo0,sidewall,ascend,sprint,1,3,1.0,,,0,True,3.5102512319825148,1.2522033525119995,0.3102512319825146, +sidewall-descend-sprint-mm1-gap3-dym1p0-wo0,sidewall,descend,sprint,1,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap3-dym2p0-wo0,sidewall,descend,sprint,1,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap4-dy0p0-wo0,sidewall,flat,sprint,1,4,0.0,,,0,True,4.4298717720554635,1.2522033525119995,0.22987177205546327, +sidewall-ascend-sprint-mm1-gap4-dy1p0-wo0,sidewall,ascend,sprint,1,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap4-dym1p0-wo0,sidewall,descend,sprint,1,4,-1.0,,,0,True,4.979500436003543,1.2522033525119995,0.7795004360035431, +sidewall-descend-sprint-mm1-gap4-dym2p0-wo0,sidewall,descend,sprint,1,4,-2.0,,,0,True,5.407036052604428,1.2522033525119995,1.207036052604428, +sidewall-flat-sprint-mm1-gap5-dy0p0-wo0,sidewall,flat,sprint,1,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap5-dy1p0-wo0,sidewall,ascend,sprint,1,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap5-dym1p0-wo0,sidewall,descend,sprint,1,5,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap5-dym2p0-wo0,sidewall,descend,sprint,1,5,-2.0,,,0,True,5.407036052604428,1.2522033525119995,0.20703605260442792, +sidewall-flat-sprint-mm1-gap6-dy0p0-wo0,sidewall,flat,sprint,1,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap6-dy1p0-wo0,sidewall,ascend,sprint,1,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap6-dym1p0-wo0,sidewall,descend,sprint,1,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap6-dym2p0-wo0,sidewall,descend,sprint,1,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap7-dy0p0-wo0,sidewall,flat,sprint,1,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap7-dy1p0-wo0,sidewall,ascend,sprint,1,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap7-dym1p0-wo0,sidewall,descend,sprint,1,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap7-dym2p0-wo0,sidewall,descend,sprint,1,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap0-dy0p0-wo1,sidewall,flat,sprint,1,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap0-dy1p0-wo1,sidewall,ascend,sprint,1,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap0-dym1p0-wo1,sidewall,descend,sprint,1,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap0-dym2p0-wo1,sidewall,descend,sprint,1,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap1-dy0p0-wo1,sidewall,flat,sprint,1,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap1-dy1p0-wo1,sidewall,ascend,sprint,1,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap1-dym1p0-wo1,sidewall,descend,sprint,1,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap1-dym2p0-wo1,sidewall,descend,sprint,1,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap2-dy0p0-wo1,sidewall,flat,sprint,1,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap2-dy1p0-wo1,sidewall,ascend,sprint,1,2,1.0,,,1,True,3.5102512319825148,1.2522033525119995,1.3102512319825146, +sidewall-descend-sprint-mm1-gap2-dym1p0-wo1,sidewall,descend,sprint,1,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap2-dym2p0-wo1,sidewall,descend,sprint,1,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap3-dy0p0-wo1,sidewall,flat,sprint,1,3,0.0,,,1,True,4.4298717720554635,1.2522033525119995,1.2298717720554633, +sidewall-ascend-sprint-mm1-gap3-dy1p0-wo1,sidewall,ascend,sprint,1,3,1.0,,,1,True,3.5102512319825148,1.2522033525119995,0.3102512319825146, +sidewall-descend-sprint-mm1-gap3-dym1p0-wo1,sidewall,descend,sprint,1,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap3-dym2p0-wo1,sidewall,descend,sprint,1,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap4-dy0p0-wo1,sidewall,flat,sprint,1,4,0.0,,,1,True,4.4298717720554635,1.2522033525119995,0.22987177205546327, +sidewall-ascend-sprint-mm1-gap4-dy1p0-wo1,sidewall,ascend,sprint,1,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap4-dym1p0-wo1,sidewall,descend,sprint,1,4,-1.0,,,1,True,4.979500436003543,1.2522033525119995,0.7795004360035431, +sidewall-descend-sprint-mm1-gap4-dym2p0-wo1,sidewall,descend,sprint,1,4,-2.0,,,1,True,5.407036052604428,1.2522033525119995,1.207036052604428, +sidewall-flat-sprint-mm1-gap5-dy0p0-wo1,sidewall,flat,sprint,1,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap5-dy1p0-wo1,sidewall,ascend,sprint,1,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap5-dym1p0-wo1,sidewall,descend,sprint,1,5,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap5-dym2p0-wo1,sidewall,descend,sprint,1,5,-2.0,,,1,True,5.407036052604428,1.2522033525119995,0.20703605260442792, +sidewall-flat-sprint-mm1-gap6-dy0p0-wo1,sidewall,flat,sprint,1,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap6-dy1p0-wo1,sidewall,ascend,sprint,1,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap6-dym1p0-wo1,sidewall,descend,sprint,1,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap6-dym2p0-wo1,sidewall,descend,sprint,1,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm1-gap7-dy0p0-wo1,sidewall,flat,sprint,1,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm1-gap7-dy1p0-wo1,sidewall,ascend,sprint,1,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap7-dym1p0-wo1,sidewall,descend,sprint,1,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm1-gap7-dym2p0-wo1,sidewall,descend,sprint,1,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap0-dy0p0-wo0,sidewall,flat,sprint,2,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap0-dy1p0-wo0,sidewall,ascend,sprint,2,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap0-dym1p0-wo0,sidewall,descend,sprint,2,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap0-dym2p0-wo0,sidewall,descend,sprint,2,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap1-dy0p0-wo0,sidewall,flat,sprint,2,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap1-dy1p0-wo0,sidewall,ascend,sprint,2,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap1-dym1p0-wo0,sidewall,descend,sprint,2,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap1-dym2p0-wo0,sidewall,descend,sprint,2,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap2-dy0p0-wo0,sidewall,flat,sprint,2,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap2-dy1p0-wo0,sidewall,ascend,sprint,2,2,1.0,,,0,True,3.7369092700269855,1.2522033525119995,1.5369092700269853, +sidewall-descend-sprint-mm2-gap2-dym1p0-wo0,sidewall,descend,sprint,2,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap2-dym2p0-wo0,sidewall,descend,sprint,2,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap3-dy0p0-wo0,sidewall,flat,sprint,2,3,0.0,,,0,True,4.707454052210679,1.2522033525119995,1.5074540522106785, +sidewall-ascend-sprint-mm2-gap3-dy1p0-wo0,sidewall,ascend,sprint,2,3,1.0,,,0,True,3.7369092700269855,1.2522033525119995,0.5369092700269853, +sidewall-descend-sprint-mm2-gap3-dym1p0-wo0,sidewall,descend,sprint,2,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap3-dym2p0-wo0,sidewall,descend,sprint,2,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap4-dy0p0-wo0,sidewall,flat,sprint,2,4,0.0,,,0,True,4.707454052210679,1.2522033525119995,0.5074540522106785, +sidewall-ascend-sprint-mm2-gap4-dy1p0-wo0,sidewall,ascend,sprint,2,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap4-dym1p0-wo0,sidewall,descend,sprint,2,4,-1.0,,,0,True,5.282354425837941,1.2522033525119995,1.0823544258379405, +sidewall-descend-sprint-mm2-gap4-dym2p0-wo0,sidewall,descend,sprint,2,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap5-dy0p0-wo0,sidewall,flat,sprint,2,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap5-dy1p0-wo0,sidewall,ascend,sprint,2,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap5-dym1p0-wo0,sidewall,descend,sprint,2,5,-1.0,,,0,True,5.282354425837941,1.2522033525119995,0.0823544258379405, +sidewall-descend-sprint-mm2-gap5-dym2p0-wo0,sidewall,descend,sprint,2,5,-2.0,,,0,True,5.727079409148399,1.2522033525119995,0.5270794091483992, +sidewall-flat-sprint-mm2-gap6-dy0p0-wo0,sidewall,flat,sprint,2,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap6-dy1p0-wo0,sidewall,ascend,sprint,2,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap6-dym1p0-wo0,sidewall,descend,sprint,2,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap6-dym2p0-wo0,sidewall,descend,sprint,2,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap7-dy0p0-wo0,sidewall,flat,sprint,2,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap7-dy1p0-wo0,sidewall,ascend,sprint,2,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap7-dym1p0-wo0,sidewall,descend,sprint,2,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap7-dym2p0-wo0,sidewall,descend,sprint,2,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap0-dy0p0-wo1,sidewall,flat,sprint,2,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap0-dy1p0-wo1,sidewall,ascend,sprint,2,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap0-dym1p0-wo1,sidewall,descend,sprint,2,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap0-dym2p0-wo1,sidewall,descend,sprint,2,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap1-dy0p0-wo1,sidewall,flat,sprint,2,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap1-dy1p0-wo1,sidewall,ascend,sprint,2,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap1-dym1p0-wo1,sidewall,descend,sprint,2,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap1-dym2p0-wo1,sidewall,descend,sprint,2,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap2-dy0p0-wo1,sidewall,flat,sprint,2,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap2-dy1p0-wo1,sidewall,ascend,sprint,2,2,1.0,,,1,True,3.7369092700269855,1.2522033525119995,1.5369092700269853, +sidewall-descend-sprint-mm2-gap2-dym1p0-wo1,sidewall,descend,sprint,2,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap2-dym2p0-wo1,sidewall,descend,sprint,2,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap3-dy0p0-wo1,sidewall,flat,sprint,2,3,0.0,,,1,True,4.707454052210679,1.2522033525119995,1.5074540522106785, +sidewall-ascend-sprint-mm2-gap3-dy1p0-wo1,sidewall,ascend,sprint,2,3,1.0,,,1,True,3.7369092700269855,1.2522033525119995,0.5369092700269853, +sidewall-descend-sprint-mm2-gap3-dym1p0-wo1,sidewall,descend,sprint,2,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap3-dym2p0-wo1,sidewall,descend,sprint,2,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap4-dy0p0-wo1,sidewall,flat,sprint,2,4,0.0,,,1,True,4.707454052210679,1.2522033525119995,0.5074540522106785, +sidewall-ascend-sprint-mm2-gap4-dy1p0-wo1,sidewall,ascend,sprint,2,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap4-dym1p0-wo1,sidewall,descend,sprint,2,4,-1.0,,,1,True,5.282354425837941,1.2522033525119995,1.0823544258379405, +sidewall-descend-sprint-mm2-gap4-dym2p0-wo1,sidewall,descend,sprint,2,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap5-dy0p0-wo1,sidewall,flat,sprint,2,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap5-dy1p0-wo1,sidewall,ascend,sprint,2,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap5-dym1p0-wo1,sidewall,descend,sprint,2,5,-1.0,,,1,True,5.282354425837941,1.2522033525119995,0.0823544258379405, +sidewall-descend-sprint-mm2-gap5-dym2p0-wo1,sidewall,descend,sprint,2,5,-2.0,,,1,True,5.727079409148399,1.2522033525119995,0.5270794091483992, +sidewall-flat-sprint-mm2-gap6-dy0p0-wo1,sidewall,flat,sprint,2,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap6-dy1p0-wo1,sidewall,ascend,sprint,2,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap6-dym1p0-wo1,sidewall,descend,sprint,2,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap6-dym2p0-wo1,sidewall,descend,sprint,2,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm2-gap7-dy0p0-wo1,sidewall,flat,sprint,2,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm2-gap7-dy1p0-wo1,sidewall,ascend,sprint,2,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap7-dym1p0-wo1,sidewall,descend,sprint,2,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm2-gap7-dym2p0-wo1,sidewall,descend,sprint,2,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap0-dy0p0-wo0,sidewall,flat,sprint,3,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap0-dy1p0-wo0,sidewall,ascend,sprint,3,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap0-dym1p0-wo0,sidewall,descend,sprint,3,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap0-dym2p0-wo0,sidewall,descend,sprint,3,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap1-dy0p0-wo0,sidewall,flat,sprint,3,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap1-dy1p0-wo0,sidewall,ascend,sprint,3,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap1-dym1p0-wo0,sidewall,descend,sprint,3,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap1-dym2p0-wo0,sidewall,descend,sprint,3,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap2-dy0p0-wo0,sidewall,flat,sprint,3,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap2-dy1p0-wo0,sidewall,ascend,sprint,3,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap2-dym1p0-wo0,sidewall,descend,sprint,3,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap2-dym2p0-wo0,sidewall,descend,sprint,3,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap3-dy0p0-wo0,sidewall,flat,sprint,3,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap3-dy1p0-wo0,sidewall,ascend,sprint,3,3,1.0,,,0,True,3.8606645587992663,1.2522033525119995,0.6606645587992661, +sidewall-descend-sprint-mm3-gap3-dym1p0-wo0,sidewall,descend,sprint,3,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap3-dym2p0-wo0,sidewall,descend,sprint,3,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap4-dy0p0-wo0,sidewall,flat,sprint,3,4,0.0,,,0,True,4.859013977175426,1.2522033525119995,0.6590139771754258, +sidewall-ascend-sprint-mm3-gap4-dy1p0-wo0,sidewall,ascend,sprint,3,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap4-dym1p0-wo0,sidewall,descend,sprint,3,4,-1.0,,,0,True,5.447712704287522,1.2522033525119995,1.2477127042875216, +sidewall-descend-sprint-mm3-gap4-dym2p0-wo0,sidewall,descend,sprint,3,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap5-dy0p0-wo0,sidewall,flat,sprint,3,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap5-dy1p0-wo0,sidewall,ascend,sprint,3,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap5-dym1p0-wo0,sidewall,descend,sprint,3,5,-1.0,,,0,True,5.447712704287522,1.2522033525119995,0.24771270428752157, +sidewall-descend-sprint-mm3-gap5-dym2p0-wo0,sidewall,descend,sprint,3,5,-2.0,,,0,True,5.901823081821408,1.2522033525119995,0.7018230818214075, +sidewall-flat-sprint-mm3-gap6-dy0p0-wo0,sidewall,flat,sprint,3,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap6-dy1p0-wo0,sidewall,ascend,sprint,3,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap6-dym1p0-wo0,sidewall,descend,sprint,3,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap6-dym2p0-wo0,sidewall,descend,sprint,3,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap7-dy0p0-wo0,sidewall,flat,sprint,3,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap7-dy1p0-wo0,sidewall,ascend,sprint,3,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap7-dym1p0-wo0,sidewall,descend,sprint,3,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap7-dym2p0-wo0,sidewall,descend,sprint,3,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap0-dy0p0-wo1,sidewall,flat,sprint,3,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap0-dy1p0-wo1,sidewall,ascend,sprint,3,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap0-dym1p0-wo1,sidewall,descend,sprint,3,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap0-dym2p0-wo1,sidewall,descend,sprint,3,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap1-dy0p0-wo1,sidewall,flat,sprint,3,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap1-dy1p0-wo1,sidewall,ascend,sprint,3,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap1-dym1p0-wo1,sidewall,descend,sprint,3,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap1-dym2p0-wo1,sidewall,descend,sprint,3,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap2-dy0p0-wo1,sidewall,flat,sprint,3,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap2-dy1p0-wo1,sidewall,ascend,sprint,3,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap2-dym1p0-wo1,sidewall,descend,sprint,3,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap2-dym2p0-wo1,sidewall,descend,sprint,3,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap3-dy0p0-wo1,sidewall,flat,sprint,3,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap3-dy1p0-wo1,sidewall,ascend,sprint,3,3,1.0,,,1,True,3.8606645587992663,1.2522033525119995,0.6606645587992661, +sidewall-descend-sprint-mm3-gap3-dym1p0-wo1,sidewall,descend,sprint,3,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap3-dym2p0-wo1,sidewall,descend,sprint,3,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap4-dy0p0-wo1,sidewall,flat,sprint,3,4,0.0,,,1,True,4.859013977175426,1.2522033525119995,0.6590139771754258, +sidewall-ascend-sprint-mm3-gap4-dy1p0-wo1,sidewall,ascend,sprint,3,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap4-dym1p0-wo1,sidewall,descend,sprint,3,4,-1.0,,,1,True,5.447712704287522,1.2522033525119995,1.2477127042875216, +sidewall-descend-sprint-mm3-gap4-dym2p0-wo1,sidewall,descend,sprint,3,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap5-dy0p0-wo1,sidewall,flat,sprint,3,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap5-dy1p0-wo1,sidewall,ascend,sprint,3,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap5-dym1p0-wo1,sidewall,descend,sprint,3,5,-1.0,,,1,True,5.447712704287522,1.2522033525119995,0.24771270428752157, +sidewall-descend-sprint-mm3-gap5-dym2p0-wo1,sidewall,descend,sprint,3,5,-2.0,,,1,True,5.901823081821408,1.2522033525119995,0.7018230818214075, +sidewall-flat-sprint-mm3-gap6-dy0p0-wo1,sidewall,flat,sprint,3,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap6-dy1p0-wo1,sidewall,ascend,sprint,3,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap6-dym1p0-wo1,sidewall,descend,sprint,3,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap6-dym2p0-wo1,sidewall,descend,sprint,3,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm3-gap7-dy0p0-wo1,sidewall,flat,sprint,3,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm3-gap7-dy1p0-wo1,sidewall,ascend,sprint,3,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap7-dym1p0-wo1,sidewall,descend,sprint,3,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm3-gap7-dym2p0-wo1,sidewall,descend,sprint,3,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap0-dy0p0-wo0,sidewall,flat,sprint,4,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap0-dy1p0-wo0,sidewall,ascend,sprint,4,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap0-dym1p0-wo0,sidewall,descend,sprint,4,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap0-dym2p0-wo0,sidewall,descend,sprint,4,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap1-dy0p0-wo0,sidewall,flat,sprint,4,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap1-dy1p0-wo0,sidewall,ascend,sprint,4,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap1-dym1p0-wo0,sidewall,descend,sprint,4,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap1-dym2p0-wo0,sidewall,descend,sprint,4,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap2-dy0p0-wo0,sidewall,flat,sprint,4,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap2-dy1p0-wo0,sidewall,ascend,sprint,4,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap2-dym1p0-wo0,sidewall,descend,sprint,4,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap2-dym2p0-wo0,sidewall,descend,sprint,4,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap3-dy0p0-wo0,sidewall,flat,sprint,4,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap3-dy1p0-wo0,sidewall,ascend,sprint,4,3,1.0,,,0,True,3.928234946468933,1.2522033525119995,0.7282349464689326, +sidewall-descend-sprint-mm4-gap3-dym1p0-wo0,sidewall,descend,sprint,4,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap3-dym2p0-wo0,sidewall,descend,sprint,4,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap4-dy0p0-wo0,sidewall,flat,sprint,4,4,0.0,,,0,True,4.941765696206179,1.2522033525119995,0.7417656962061789, +sidewall-ascend-sprint-mm4-gap4-dy1p0-wo0,sidewall,ascend,sprint,4,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap4-dym1p0-wo0,sidewall,descend,sprint,4,4,-1.0,,,0,True,5.537998324320994,1.2522033525119995,1.3379983243209939, +sidewall-descend-sprint-mm4-gap4-dym2p0-wo0,sidewall,descend,sprint,4,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap5-dy0p0-wo0,sidewall,flat,sprint,4,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap5-dy1p0-wo0,sidewall,ascend,sprint,4,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap5-dym1p0-wo0,sidewall,descend,sprint,4,5,-1.0,,,0,True,5.537998324320994,1.2522033525119995,0.33799832432099386, +sidewall-descend-sprint-mm4-gap5-dym2p0-wo0,sidewall,descend,sprint,4,5,-2.0,,,0,True,5.997233127100872,1.2522033525119995,0.7972331271008715, +sidewall-flat-sprint-mm4-gap6-dy0p0-wo0,sidewall,flat,sprint,4,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap6-dy1p0-wo0,sidewall,ascend,sprint,4,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap6-dym1p0-wo0,sidewall,descend,sprint,4,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap6-dym2p0-wo0,sidewall,descend,sprint,4,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap7-dy0p0-wo0,sidewall,flat,sprint,4,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap7-dy1p0-wo0,sidewall,ascend,sprint,4,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap7-dym1p0-wo0,sidewall,descend,sprint,4,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap7-dym2p0-wo0,sidewall,descend,sprint,4,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap0-dy0p0-wo1,sidewall,flat,sprint,4,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap0-dy1p0-wo1,sidewall,ascend,sprint,4,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap0-dym1p0-wo1,sidewall,descend,sprint,4,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap0-dym2p0-wo1,sidewall,descend,sprint,4,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap1-dy0p0-wo1,sidewall,flat,sprint,4,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap1-dy1p0-wo1,sidewall,ascend,sprint,4,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap1-dym1p0-wo1,sidewall,descend,sprint,4,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap1-dym2p0-wo1,sidewall,descend,sprint,4,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap2-dy0p0-wo1,sidewall,flat,sprint,4,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap2-dy1p0-wo1,sidewall,ascend,sprint,4,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap2-dym1p0-wo1,sidewall,descend,sprint,4,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap2-dym2p0-wo1,sidewall,descend,sprint,4,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap3-dy0p0-wo1,sidewall,flat,sprint,4,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap3-dy1p0-wo1,sidewall,ascend,sprint,4,3,1.0,,,1,True,3.928234946468933,1.2522033525119995,0.7282349464689326, +sidewall-descend-sprint-mm4-gap3-dym1p0-wo1,sidewall,descend,sprint,4,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap3-dym2p0-wo1,sidewall,descend,sprint,4,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap4-dy0p0-wo1,sidewall,flat,sprint,4,4,0.0,,,1,True,4.941765696206179,1.2522033525119995,0.7417656962061789, +sidewall-ascend-sprint-mm4-gap4-dy1p0-wo1,sidewall,ascend,sprint,4,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap4-dym1p0-wo1,sidewall,descend,sprint,4,4,-1.0,,,1,True,5.537998324320994,1.2522033525119995,1.3379983243209939, +sidewall-descend-sprint-mm4-gap4-dym2p0-wo1,sidewall,descend,sprint,4,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap5-dy0p0-wo1,sidewall,flat,sprint,4,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap5-dy1p0-wo1,sidewall,ascend,sprint,4,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap5-dym1p0-wo1,sidewall,descend,sprint,4,5,-1.0,,,1,True,5.537998324320994,1.2522033525119995,0.33799832432099386, +sidewall-descend-sprint-mm4-gap5-dym2p0-wo1,sidewall,descend,sprint,4,5,-2.0,,,1,True,5.997233127100872,1.2522033525119995,0.7972331271008715, +sidewall-flat-sprint-mm4-gap6-dy0p0-wo1,sidewall,flat,sprint,4,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap6-dy1p0-wo1,sidewall,ascend,sprint,4,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap6-dym1p0-wo1,sidewall,descend,sprint,4,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap6-dym2p0-wo1,sidewall,descend,sprint,4,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm4-gap7-dy0p0-wo1,sidewall,flat,sprint,4,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm4-gap7-dy1p0-wo1,sidewall,ascend,sprint,4,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap7-dym1p0-wo1,sidewall,descend,sprint,4,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm4-gap7-dym2p0-wo1,sidewall,descend,sprint,4,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap0-dy0p0-wo0,sidewall,flat,sprint,5,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap0-dy1p0-wo0,sidewall,ascend,sprint,5,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap0-dym1p0-wo0,sidewall,descend,sprint,5,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap0-dym2p0-wo0,sidewall,descend,sprint,5,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap1-dy0p0-wo0,sidewall,flat,sprint,5,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap1-dy1p0-wo0,sidewall,ascend,sprint,5,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap1-dym1p0-wo0,sidewall,descend,sprint,5,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap1-dym2p0-wo0,sidewall,descend,sprint,5,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap2-dy0p0-wo0,sidewall,flat,sprint,5,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap2-dy1p0-wo0,sidewall,ascend,sprint,5,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap2-dym1p0-wo0,sidewall,descend,sprint,5,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap2-dym2p0-wo0,sidewall,descend,sprint,5,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap3-dy0p0-wo0,sidewall,flat,sprint,5,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap3-dy1p0-wo0,sidewall,ascend,sprint,5,3,1.0,,,0,True,3.9651283781365696,1.2522033525119995,0.7651283781365694, +sidewall-descend-sprint-mm5-gap3-dym1p0-wo0,sidewall,descend,sprint,5,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap3-dym2p0-wo0,sidewall,descend,sprint,5,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap4-dy0p0-wo0,sidewall,flat,sprint,5,4,0.0,,,0,True,4.986948134796969,1.2522033525119995,0.7869481347969689, +sidewall-ascend-sprint-mm5-gap4-dy1p0-wo0,sidewall,ascend,sprint,5,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap4-dym1p0-wo0,sidewall,descend,sprint,5,4,-1.0,,,0,True,5.5872942728592685,1.2522033525119995,1.3872942728592683, +sidewall-descend-sprint-mm5-gap4-dym2p0-wo0,sidewall,descend,sprint,5,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap5-dy0p0-wo0,sidewall,flat,sprint,5,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap5-dy1p0-wo0,sidewall,ascend,sprint,5,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap5-dym1p0-wo0,sidewall,descend,sprint,5,5,-1.0,,,0,True,5.5872942728592685,1.2522033525119995,0.3872942728592683, +sidewall-descend-sprint-mm5-gap5-dym2p0-wo0,sidewall,descend,sprint,5,5,-2.0,,,0,True,6.049327011823457,1.2522033525119995,0.8493270118234566, +sidewall-flat-sprint-mm5-gap6-dy0p0-wo0,sidewall,flat,sprint,5,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap6-dy1p0-wo0,sidewall,ascend,sprint,5,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap6-dym1p0-wo0,sidewall,descend,sprint,5,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap6-dym2p0-wo0,sidewall,descend,sprint,5,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap7-dy0p0-wo0,sidewall,flat,sprint,5,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap7-dy1p0-wo0,sidewall,ascend,sprint,5,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap7-dym1p0-wo0,sidewall,descend,sprint,5,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap7-dym2p0-wo0,sidewall,descend,sprint,5,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap0-dy0p0-wo1,sidewall,flat,sprint,5,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap0-dy1p0-wo1,sidewall,ascend,sprint,5,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap0-dym1p0-wo1,sidewall,descend,sprint,5,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap0-dym2p0-wo1,sidewall,descend,sprint,5,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap1-dy0p0-wo1,sidewall,flat,sprint,5,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap1-dy1p0-wo1,sidewall,ascend,sprint,5,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap1-dym1p0-wo1,sidewall,descend,sprint,5,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap1-dym2p0-wo1,sidewall,descend,sprint,5,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap2-dy0p0-wo1,sidewall,flat,sprint,5,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap2-dy1p0-wo1,sidewall,ascend,sprint,5,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap2-dym1p0-wo1,sidewall,descend,sprint,5,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap2-dym2p0-wo1,sidewall,descend,sprint,5,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap3-dy0p0-wo1,sidewall,flat,sprint,5,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap3-dy1p0-wo1,sidewall,ascend,sprint,5,3,1.0,,,1,True,3.9651283781365696,1.2522033525119995,0.7651283781365694, +sidewall-descend-sprint-mm5-gap3-dym1p0-wo1,sidewall,descend,sprint,5,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap3-dym2p0-wo1,sidewall,descend,sprint,5,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap4-dy0p0-wo1,sidewall,flat,sprint,5,4,0.0,,,1,True,4.986948134796969,1.2522033525119995,0.7869481347969689, +sidewall-ascend-sprint-mm5-gap4-dy1p0-wo1,sidewall,ascend,sprint,5,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap4-dym1p0-wo1,sidewall,descend,sprint,5,4,-1.0,,,1,True,5.5872942728592685,1.2522033525119995,1.3872942728592683, +sidewall-descend-sprint-mm5-gap4-dym2p0-wo1,sidewall,descend,sprint,5,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap5-dy0p0-wo1,sidewall,flat,sprint,5,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap5-dy1p0-wo1,sidewall,ascend,sprint,5,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap5-dym1p0-wo1,sidewall,descend,sprint,5,5,-1.0,,,1,True,5.5872942728592685,1.2522033525119995,0.3872942728592683, +sidewall-descend-sprint-mm5-gap5-dym2p0-wo1,sidewall,descend,sprint,5,5,-2.0,,,1,True,6.049327011823457,1.2522033525119995,0.8493270118234566, +sidewall-flat-sprint-mm5-gap6-dy0p0-wo1,sidewall,flat,sprint,5,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap6-dy1p0-wo1,sidewall,ascend,sprint,5,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap6-dym1p0-wo1,sidewall,descend,sprint,5,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap6-dym2p0-wo1,sidewall,descend,sprint,5,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm5-gap7-dy0p0-wo1,sidewall,flat,sprint,5,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm5-gap7-dy1p0-wo1,sidewall,ascend,sprint,5,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap7-dym1p0-wo1,sidewall,descend,sprint,5,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm5-gap7-dym2p0-wo1,sidewall,descend,sprint,5,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap0-dy0p0-wo0,sidewall,flat,sprint,6,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap0-dy1p0-wo0,sidewall,ascend,sprint,6,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap0-dym1p0-wo0,sidewall,descend,sprint,6,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap0-dym2p0-wo0,sidewall,descend,sprint,6,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap1-dy0p0-wo0,sidewall,flat,sprint,6,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap1-dy1p0-wo0,sidewall,ascend,sprint,6,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap1-dym1p0-wo0,sidewall,descend,sprint,6,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap1-dym2p0-wo0,sidewall,descend,sprint,6,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap2-dy0p0-wo0,sidewall,flat,sprint,6,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap2-dy1p0-wo0,sidewall,ascend,sprint,6,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap2-dym1p0-wo0,sidewall,descend,sprint,6,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap2-dym2p0-wo0,sidewall,descend,sprint,6,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap3-dy0p0-wo0,sidewall,flat,sprint,6,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap3-dy1p0-wo0,sidewall,ascend,sprint,6,3,1.0,,,0,True,3.9852721918270992,1.2522033525119995,0.7852721918270991, +sidewall-descend-sprint-mm6-gap3-dym1p0-wo0,sidewall,descend,sprint,6,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap3-dym2p0-wo0,sidewall,descend,sprint,6,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap4-dy0p0-wo0,sidewall,flat,sprint,6,4,0.0,,,0,True,5.01161774626754,1.2522033525119995,0.8116177462675402, +sidewall-ascend-sprint-mm6-gap4-dy1p0-wo0,sidewall,ascend,sprint,6,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap4-dym1p0-wo0,sidewall,descend,sprint,6,4,-1.0,,,0,True,5.614209860761167,1.2522033525119995,1.4142098607611668, +sidewall-descend-sprint-mm6-gap4-dym2p0-wo0,sidewall,descend,sprint,6,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap5-dy0p0-wo0,sidewall,flat,sprint,6,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap5-dy1p0-wo0,sidewall,ascend,sprint,6,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap5-dym1p0-wo0,sidewall,descend,sprint,6,5,-1.0,,,0,True,5.614209860761167,1.2522033525119995,0.41420986076116684, +sidewall-descend-sprint-mm6-gap5-dym2p0-wo0,sidewall,descend,sprint,6,5,-2.0,,,0,True,6.07777027288199,1.2522033525119995,0.8777702728819898, +sidewall-flat-sprint-mm6-gap6-dy0p0-wo0,sidewall,flat,sprint,6,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap6-dy1p0-wo0,sidewall,ascend,sprint,6,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap6-dym1p0-wo0,sidewall,descend,sprint,6,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap6-dym2p0-wo0,sidewall,descend,sprint,6,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap7-dy0p0-wo0,sidewall,flat,sprint,6,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap7-dy1p0-wo0,sidewall,ascend,sprint,6,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap7-dym1p0-wo0,sidewall,descend,sprint,6,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap7-dym2p0-wo0,sidewall,descend,sprint,6,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap0-dy0p0-wo1,sidewall,flat,sprint,6,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap0-dy1p0-wo1,sidewall,ascend,sprint,6,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap0-dym1p0-wo1,sidewall,descend,sprint,6,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap0-dym2p0-wo1,sidewall,descend,sprint,6,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap1-dy0p0-wo1,sidewall,flat,sprint,6,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap1-dy1p0-wo1,sidewall,ascend,sprint,6,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap1-dym1p0-wo1,sidewall,descend,sprint,6,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap1-dym2p0-wo1,sidewall,descend,sprint,6,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap2-dy0p0-wo1,sidewall,flat,sprint,6,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap2-dy1p0-wo1,sidewall,ascend,sprint,6,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap2-dym1p0-wo1,sidewall,descend,sprint,6,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap2-dym2p0-wo1,sidewall,descend,sprint,6,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap3-dy0p0-wo1,sidewall,flat,sprint,6,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap3-dy1p0-wo1,sidewall,ascend,sprint,6,3,1.0,,,1,True,3.9852721918270992,1.2522033525119995,0.7852721918270991, +sidewall-descend-sprint-mm6-gap3-dym1p0-wo1,sidewall,descend,sprint,6,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap3-dym2p0-wo1,sidewall,descend,sprint,6,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap4-dy0p0-wo1,sidewall,flat,sprint,6,4,0.0,,,1,True,5.01161774626754,1.2522033525119995,0.8116177462675402, +sidewall-ascend-sprint-mm6-gap4-dy1p0-wo1,sidewall,ascend,sprint,6,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap4-dym1p0-wo1,sidewall,descend,sprint,6,4,-1.0,,,1,True,5.614209860761167,1.2522033525119995,1.4142098607611668, +sidewall-descend-sprint-mm6-gap4-dym2p0-wo1,sidewall,descend,sprint,6,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap5-dy0p0-wo1,sidewall,flat,sprint,6,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap5-dy1p0-wo1,sidewall,ascend,sprint,6,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap5-dym1p0-wo1,sidewall,descend,sprint,6,5,-1.0,,,1,True,5.614209860761167,1.2522033525119995,0.41420986076116684, +sidewall-descend-sprint-mm6-gap5-dym2p0-wo1,sidewall,descend,sprint,6,5,-2.0,,,1,True,6.07777027288199,1.2522033525119995,0.8777702728819898, +sidewall-flat-sprint-mm6-gap6-dy0p0-wo1,sidewall,flat,sprint,6,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap6-dy1p0-wo1,sidewall,ascend,sprint,6,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap6-dym1p0-wo1,sidewall,descend,sprint,6,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap6-dym2p0-wo1,sidewall,descend,sprint,6,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm6-gap7-dy0p0-wo1,sidewall,flat,sprint,6,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm6-gap7-dy1p0-wo1,sidewall,ascend,sprint,6,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap7-dym1p0-wo1,sidewall,descend,sprint,6,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm6-gap7-dym2p0-wo1,sidewall,descend,sprint,6,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap0-dy0p0-wo0,sidewall,flat,sprint,7,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap0-dy1p0-wo0,sidewall,ascend,sprint,7,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap0-dym1p0-wo0,sidewall,descend,sprint,7,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap0-dym2p0-wo0,sidewall,descend,sprint,7,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap1-dy0p0-wo0,sidewall,flat,sprint,7,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap1-dy1p0-wo0,sidewall,ascend,sprint,7,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap1-dym1p0-wo0,sidewall,descend,sprint,7,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap1-dym2p0-wo0,sidewall,descend,sprint,7,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap2-dy0p0-wo0,sidewall,flat,sprint,7,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap2-dy1p0-wo0,sidewall,ascend,sprint,7,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap2-dym1p0-wo0,sidewall,descend,sprint,7,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap2-dym2p0-wo0,sidewall,descend,sprint,7,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap3-dy0p0-wo0,sidewall,flat,sprint,7,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap3-dy1p0-wo0,sidewall,ascend,sprint,7,3,1.0,,,0,True,3.9962707141021294,1.2522033525119995,0.7962707141021292, +sidewall-descend-sprint-mm7-gap3-dym1p0-wo0,sidewall,descend,sprint,7,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap3-dym2p0-wo0,sidewall,descend,sprint,7,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap4-dy0p0-wo0,sidewall,flat,sprint,7,4,0.0,,,0,True,5.025087354130473,1.2522033525119995,0.8250873541304724, +sidewall-ascend-sprint-mm7-gap4-dy1p0-wo0,sidewall,ascend,sprint,7,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap4-dym1p0-wo0,sidewall,descend,sprint,7,4,-1.0,,,0,True,5.628905771755603,1.2522033525119995,1.4289057717556028, +sidewall-descend-sprint-mm7-gap4-dym2p0-wo0,sidewall,descend,sprint,7,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap5-dy0p0-wo0,sidewall,flat,sprint,7,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap5-dy1p0-wo0,sidewall,ascend,sprint,7,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap5-dym1p0-wo0,sidewall,descend,sprint,7,5,-1.0,,,0,True,5.628905771755603,1.2522033525119995,0.42890577175560285, +sidewall-descend-sprint-mm7-gap5-dym2p0-wo0,sidewall,descend,sprint,7,5,-2.0,,,0,True,6.093300293419947,1.2522033525119995,0.893300293419947, +sidewall-flat-sprint-mm7-gap6-dy0p0-wo0,sidewall,flat,sprint,7,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap6-dy1p0-wo0,sidewall,ascend,sprint,7,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap6-dym1p0-wo0,sidewall,descend,sprint,7,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap6-dym2p0-wo0,sidewall,descend,sprint,7,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap7-dy0p0-wo0,sidewall,flat,sprint,7,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap7-dy1p0-wo0,sidewall,ascend,sprint,7,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap7-dym1p0-wo0,sidewall,descend,sprint,7,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap7-dym2p0-wo0,sidewall,descend,sprint,7,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap0-dy0p0-wo1,sidewall,flat,sprint,7,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap0-dy1p0-wo1,sidewall,ascend,sprint,7,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap0-dym1p0-wo1,sidewall,descend,sprint,7,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap0-dym2p0-wo1,sidewall,descend,sprint,7,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap1-dy0p0-wo1,sidewall,flat,sprint,7,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap1-dy1p0-wo1,sidewall,ascend,sprint,7,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap1-dym1p0-wo1,sidewall,descend,sprint,7,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap1-dym2p0-wo1,sidewall,descend,sprint,7,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap2-dy0p0-wo1,sidewall,flat,sprint,7,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap2-dy1p0-wo1,sidewall,ascend,sprint,7,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap2-dym1p0-wo1,sidewall,descend,sprint,7,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap2-dym2p0-wo1,sidewall,descend,sprint,7,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap3-dy0p0-wo1,sidewall,flat,sprint,7,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap3-dy1p0-wo1,sidewall,ascend,sprint,7,3,1.0,,,1,True,3.9962707141021294,1.2522033525119995,0.7962707141021292, +sidewall-descend-sprint-mm7-gap3-dym1p0-wo1,sidewall,descend,sprint,7,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap3-dym2p0-wo1,sidewall,descend,sprint,7,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap4-dy0p0-wo1,sidewall,flat,sprint,7,4,0.0,,,1,True,5.025087354130473,1.2522033525119995,0.8250873541304724, +sidewall-ascend-sprint-mm7-gap4-dy1p0-wo1,sidewall,ascend,sprint,7,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap4-dym1p0-wo1,sidewall,descend,sprint,7,4,-1.0,,,1,True,5.628905771755603,1.2522033525119995,1.4289057717556028, +sidewall-descend-sprint-mm7-gap4-dym2p0-wo1,sidewall,descend,sprint,7,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap5-dy0p0-wo1,sidewall,flat,sprint,7,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap5-dy1p0-wo1,sidewall,ascend,sprint,7,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap5-dym1p0-wo1,sidewall,descend,sprint,7,5,-1.0,,,1,True,5.628905771755603,1.2522033525119995,0.42890577175560285, +sidewall-descend-sprint-mm7-gap5-dym2p0-wo1,sidewall,descend,sprint,7,5,-2.0,,,1,True,6.093300293419947,1.2522033525119995,0.893300293419947, +sidewall-flat-sprint-mm7-gap6-dy0p0-wo1,sidewall,flat,sprint,7,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap6-dy1p0-wo1,sidewall,ascend,sprint,7,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap6-dym1p0-wo1,sidewall,descend,sprint,7,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap6-dym2p0-wo1,sidewall,descend,sprint,7,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm7-gap7-dy0p0-wo1,sidewall,flat,sprint,7,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm7-gap7-dy1p0-wo1,sidewall,ascend,sprint,7,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap7-dym1p0-wo1,sidewall,descend,sprint,7,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm7-gap7-dym2p0-wo1,sidewall,descend,sprint,7,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap0-dy0p0-wo0,sidewall,flat,sprint,8,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap0-dy1p0-wo0,sidewall,ascend,sprint,8,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap0-dym1p0-wo0,sidewall,descend,sprint,8,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap0-dym2p0-wo0,sidewall,descend,sprint,8,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap1-dy0p0-wo0,sidewall,flat,sprint,8,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap1-dy1p0-wo0,sidewall,ascend,sprint,8,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap1-dym1p0-wo0,sidewall,descend,sprint,8,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap1-dym2p0-wo0,sidewall,descend,sprint,8,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap2-dy0p0-wo0,sidewall,flat,sprint,8,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap2-dy1p0-wo0,sidewall,ascend,sprint,8,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap2-dym1p0-wo0,sidewall,descend,sprint,8,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap2-dym2p0-wo0,sidewall,descend,sprint,8,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap3-dy0p0-wo0,sidewall,flat,sprint,8,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap3-dy1p0-wo0,sidewall,ascend,sprint,8,3,1.0,,,0,True,4.002275907264296,1.2522033525119995,0.8022759072642955, +sidewall-descend-sprint-mm8-gap3-dym1p0-wo0,sidewall,descend,sprint,8,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap3-dym2p0-wo0,sidewall,descend,sprint,8,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap4-dy0p0-wo0,sidewall,flat,sprint,8,4,0.0,,,0,True,5.032441760023634,1.2522033525119995,0.8324417600236336, +sidewall-ascend-sprint-mm8-gap4-dy1p0-wo0,sidewall,ascend,sprint,8,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap4-dym1p0-wo0,sidewall,descend,sprint,8,4,-1.0,,,0,True,5.636929739158566,1.2522033525119995,1.4369297391585656, +sidewall-descend-sprint-mm8-gap4-dym2p0-wo0,sidewall,descend,sprint,8,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap5-dy0p0-wo0,sidewall,flat,sprint,8,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap5-dy1p0-wo0,sidewall,ascend,sprint,8,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap5-dym1p0-wo0,sidewall,descend,sprint,8,5,-1.0,,,0,True,5.636929739158566,1.2522033525119995,0.4369297391585656, +sidewall-descend-sprint-mm8-gap5-dym2p0-wo0,sidewall,descend,sprint,8,5,-2.0,,,0,True,6.101779684633674,1.2522033525119995,0.9017796846336736, +sidewall-flat-sprint-mm8-gap6-dy0p0-wo0,sidewall,flat,sprint,8,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap6-dy1p0-wo0,sidewall,ascend,sprint,8,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap6-dym1p0-wo0,sidewall,descend,sprint,8,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap6-dym2p0-wo0,sidewall,descend,sprint,8,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap7-dy0p0-wo0,sidewall,flat,sprint,8,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap7-dy1p0-wo0,sidewall,ascend,sprint,8,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap7-dym1p0-wo0,sidewall,descend,sprint,8,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap7-dym2p0-wo0,sidewall,descend,sprint,8,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap0-dy0p0-wo1,sidewall,flat,sprint,8,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap0-dy1p0-wo1,sidewall,ascend,sprint,8,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap0-dym1p0-wo1,sidewall,descend,sprint,8,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap0-dym2p0-wo1,sidewall,descend,sprint,8,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap1-dy0p0-wo1,sidewall,flat,sprint,8,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap1-dy1p0-wo1,sidewall,ascend,sprint,8,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap1-dym1p0-wo1,sidewall,descend,sprint,8,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap1-dym2p0-wo1,sidewall,descend,sprint,8,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap2-dy0p0-wo1,sidewall,flat,sprint,8,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap2-dy1p0-wo1,sidewall,ascend,sprint,8,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap2-dym1p0-wo1,sidewall,descend,sprint,8,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap2-dym2p0-wo1,sidewall,descend,sprint,8,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap3-dy0p0-wo1,sidewall,flat,sprint,8,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap3-dy1p0-wo1,sidewall,ascend,sprint,8,3,1.0,,,1,True,4.002275907264296,1.2522033525119995,0.8022759072642955, +sidewall-descend-sprint-mm8-gap3-dym1p0-wo1,sidewall,descend,sprint,8,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap3-dym2p0-wo1,sidewall,descend,sprint,8,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap4-dy0p0-wo1,sidewall,flat,sprint,8,4,0.0,,,1,True,5.032441760023634,1.2522033525119995,0.8324417600236336, +sidewall-ascend-sprint-mm8-gap4-dy1p0-wo1,sidewall,ascend,sprint,8,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap4-dym1p0-wo1,sidewall,descend,sprint,8,4,-1.0,,,1,True,5.636929739158566,1.2522033525119995,1.4369297391585656, +sidewall-descend-sprint-mm8-gap4-dym2p0-wo1,sidewall,descend,sprint,8,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap5-dy0p0-wo1,sidewall,flat,sprint,8,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap5-dy1p0-wo1,sidewall,ascend,sprint,8,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap5-dym1p0-wo1,sidewall,descend,sprint,8,5,-1.0,,,1,True,5.636929739158566,1.2522033525119995,0.4369297391585656, +sidewall-descend-sprint-mm8-gap5-dym2p0-wo1,sidewall,descend,sprint,8,5,-2.0,,,1,True,6.101779684633674,1.2522033525119995,0.9017796846336736, +sidewall-flat-sprint-mm8-gap6-dy0p0-wo1,sidewall,flat,sprint,8,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap6-dy1p0-wo1,sidewall,ascend,sprint,8,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap6-dym1p0-wo1,sidewall,descend,sprint,8,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap6-dym2p0-wo1,sidewall,descend,sprint,8,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm8-gap7-dy0p0-wo1,sidewall,flat,sprint,8,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm8-gap7-dy1p0-wo1,sidewall,ascend,sprint,8,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap7-dym1p0-wo1,sidewall,descend,sprint,8,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm8-gap7-dym2p0-wo1,sidewall,descend,sprint,8,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap0-dy0p0-wo0,sidewall,flat,sprint,9,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap0-dy1p0-wo0,sidewall,ascend,sprint,9,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap0-dym1p0-wo0,sidewall,descend,sprint,9,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap0-dym2p0-wo0,sidewall,descend,sprint,9,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap1-dy0p0-wo0,sidewall,flat,sprint,9,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap1-dy1p0-wo0,sidewall,ascend,sprint,9,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap1-dym1p0-wo0,sidewall,descend,sprint,9,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap1-dym2p0-wo0,sidewall,descend,sprint,9,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap2-dy0p0-wo0,sidewall,flat,sprint,9,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap2-dy1p0-wo0,sidewall,ascend,sprint,9,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap2-dym1p0-wo0,sidewall,descend,sprint,9,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap2-dym2p0-wo0,sidewall,descend,sprint,9,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap3-dy0p0-wo0,sidewall,flat,sprint,9,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap3-dy1p0-wo0,sidewall,ascend,sprint,9,3,1.0,,,0,True,4.005554742730838,1.2522033525119995,0.8055547427308376, +sidewall-descend-sprint-mm9-gap3-dym1p0-wo0,sidewall,descend,sprint,9,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap3-dym2p0-wo0,sidewall,descend,sprint,9,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap4-dy0p0-wo0,sidewall,flat,sprint,9,4,0.0,,,0,True,5.0364572656413005,1.2522033525119995,0.8364572656413003, +sidewall-ascend-sprint-mm9-gap4-dy1p0-wo0,sidewall,ascend,sprint,9,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap4-dym1p0-wo0,sidewall,descend,sprint,9,4,-1.0,,,0,True,5.641310825360584,1.2522033525119995,1.441310825360584, +sidewall-descend-sprint-mm9-gap4-dym2p0-wo0,sidewall,descend,sprint,9,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap5-dy0p0-wo0,sidewall,flat,sprint,9,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap5-dy1p0-wo0,sidewall,ascend,sprint,9,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap5-dym1p0-wo0,sidewall,descend,sprint,9,5,-1.0,,,0,True,5.641310825360584,1.2522033525119995,0.44131082536058397, +sidewall-descend-sprint-mm9-gap5-dym2p0-wo0,sidewall,descend,sprint,9,5,-2.0,,,0,True,6.106409432236369,1.2522033525119995,0.9064094322363685, +sidewall-flat-sprint-mm9-gap6-dy0p0-wo0,sidewall,flat,sprint,9,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap6-dy1p0-wo0,sidewall,ascend,sprint,9,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap6-dym1p0-wo0,sidewall,descend,sprint,9,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap6-dym2p0-wo0,sidewall,descend,sprint,9,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap7-dy0p0-wo0,sidewall,flat,sprint,9,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap7-dy1p0-wo0,sidewall,ascend,sprint,9,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap7-dym1p0-wo0,sidewall,descend,sprint,9,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap7-dym2p0-wo0,sidewall,descend,sprint,9,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap0-dy0p0-wo1,sidewall,flat,sprint,9,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap0-dy1p0-wo1,sidewall,ascend,sprint,9,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap0-dym1p0-wo1,sidewall,descend,sprint,9,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap0-dym2p0-wo1,sidewall,descend,sprint,9,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap1-dy0p0-wo1,sidewall,flat,sprint,9,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap1-dy1p0-wo1,sidewall,ascend,sprint,9,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap1-dym1p0-wo1,sidewall,descend,sprint,9,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap1-dym2p0-wo1,sidewall,descend,sprint,9,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap2-dy0p0-wo1,sidewall,flat,sprint,9,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap2-dy1p0-wo1,sidewall,ascend,sprint,9,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap2-dym1p0-wo1,sidewall,descend,sprint,9,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap2-dym2p0-wo1,sidewall,descend,sprint,9,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap3-dy0p0-wo1,sidewall,flat,sprint,9,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap3-dy1p0-wo1,sidewall,ascend,sprint,9,3,1.0,,,1,True,4.005554742730838,1.2522033525119995,0.8055547427308376, +sidewall-descend-sprint-mm9-gap3-dym1p0-wo1,sidewall,descend,sprint,9,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap3-dym2p0-wo1,sidewall,descend,sprint,9,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap4-dy0p0-wo1,sidewall,flat,sprint,9,4,0.0,,,1,True,5.0364572656413005,1.2522033525119995,0.8364572656413003, +sidewall-ascend-sprint-mm9-gap4-dy1p0-wo1,sidewall,ascend,sprint,9,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap4-dym1p0-wo1,sidewall,descend,sprint,9,4,-1.0,,,1,True,5.641310825360584,1.2522033525119995,1.441310825360584, +sidewall-descend-sprint-mm9-gap4-dym2p0-wo1,sidewall,descend,sprint,9,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap5-dy0p0-wo1,sidewall,flat,sprint,9,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap5-dy1p0-wo1,sidewall,ascend,sprint,9,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap5-dym1p0-wo1,sidewall,descend,sprint,9,5,-1.0,,,1,True,5.641310825360584,1.2522033525119995,0.44131082536058397, +sidewall-descend-sprint-mm9-gap5-dym2p0-wo1,sidewall,descend,sprint,9,5,-2.0,,,1,True,6.106409432236369,1.2522033525119995,0.9064094322363685, +sidewall-flat-sprint-mm9-gap6-dy0p0-wo1,sidewall,flat,sprint,9,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap6-dy1p0-wo1,sidewall,ascend,sprint,9,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap6-dym1p0-wo1,sidewall,descend,sprint,9,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap6-dym2p0-wo1,sidewall,descend,sprint,9,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm9-gap7-dy0p0-wo1,sidewall,flat,sprint,9,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm9-gap7-dy1p0-wo1,sidewall,ascend,sprint,9,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap7-dym1p0-wo1,sidewall,descend,sprint,9,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm9-gap7-dym2p0-wo1,sidewall,descend,sprint,9,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap0-dy0p0-wo0,sidewall,flat,sprint,10,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap0-dy1p0-wo0,sidewall,ascend,sprint,10,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap0-dym1p0-wo0,sidewall,descend,sprint,10,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap0-dym2p0-wo0,sidewall,descend,sprint,10,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap1-dy0p0-wo0,sidewall,flat,sprint,10,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap1-dy1p0-wo0,sidewall,ascend,sprint,10,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap1-dym1p0-wo0,sidewall,descend,sprint,10,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap1-dym2p0-wo0,sidewall,descend,sprint,10,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap2-dy0p0-wo0,sidewall,flat,sprint,10,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap2-dy1p0-wo0,sidewall,ascend,sprint,10,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap2-dym1p0-wo0,sidewall,descend,sprint,10,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap2-dym2p0-wo0,sidewall,descend,sprint,10,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap3-dy0p0-wo0,sidewall,flat,sprint,10,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap3-dy1p0-wo0,sidewall,ascend,sprint,10,3,1.0,,,0,True,4.00734498689557,1.2522033525119995,0.8073449868955702, +sidewall-descend-sprint-mm10-gap3-dym1p0-wo0,sidewall,descend,sprint,10,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap3-dym2p0-wo0,sidewall,descend,sprint,10,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap4-dy0p0-wo0,sidewall,flat,sprint,10,4,0.0,,,0,True,5.038649731708546,1.2522033525119995,0.8386497317085455, +sidewall-ascend-sprint-mm10-gap4-dy1p0-wo0,sidewall,ascend,sprint,10,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap4-dym1p0-wo0,sidewall,descend,sprint,10,4,-1.0,,,0,True,5.643702898426886,1.2522033525119995,1.4437028984268858, +sidewall-descend-sprint-mm10-gap4-dym2p0-wo0,sidewall,descend,sprint,10,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap5-dy0p0-wo0,sidewall,flat,sprint,10,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap5-dy1p0-wo0,sidewall,ascend,sprint,10,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap5-dym1p0-wo0,sidewall,descend,sprint,10,5,-1.0,,,0,True,5.643702898426886,1.2522033525119995,0.4437028984268858, +sidewall-descend-sprint-mm10-gap5-dym2p0-wo0,sidewall,descend,sprint,10,5,-2.0,,,0,True,6.10893727442744,1.2522033525119995,0.9089372744274398, +sidewall-flat-sprint-mm10-gap6-dy0p0-wo0,sidewall,flat,sprint,10,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap6-dy1p0-wo0,sidewall,ascend,sprint,10,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap6-dym1p0-wo0,sidewall,descend,sprint,10,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap6-dym2p0-wo0,sidewall,descend,sprint,10,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap7-dy0p0-wo0,sidewall,flat,sprint,10,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap7-dy1p0-wo0,sidewall,ascend,sprint,10,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap7-dym1p0-wo0,sidewall,descend,sprint,10,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap7-dym2p0-wo0,sidewall,descend,sprint,10,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap0-dy0p0-wo1,sidewall,flat,sprint,10,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap0-dy1p0-wo1,sidewall,ascend,sprint,10,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap0-dym1p0-wo1,sidewall,descend,sprint,10,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap0-dym2p0-wo1,sidewall,descend,sprint,10,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap1-dy0p0-wo1,sidewall,flat,sprint,10,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap1-dy1p0-wo1,sidewall,ascend,sprint,10,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap1-dym1p0-wo1,sidewall,descend,sprint,10,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap1-dym2p0-wo1,sidewall,descend,sprint,10,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap2-dy0p0-wo1,sidewall,flat,sprint,10,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap2-dy1p0-wo1,sidewall,ascend,sprint,10,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap2-dym1p0-wo1,sidewall,descend,sprint,10,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap2-dym2p0-wo1,sidewall,descend,sprint,10,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap3-dy0p0-wo1,sidewall,flat,sprint,10,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap3-dy1p0-wo1,sidewall,ascend,sprint,10,3,1.0,,,1,True,4.00734498689557,1.2522033525119995,0.8073449868955702, +sidewall-descend-sprint-mm10-gap3-dym1p0-wo1,sidewall,descend,sprint,10,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap3-dym2p0-wo1,sidewall,descend,sprint,10,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap4-dy0p0-wo1,sidewall,flat,sprint,10,4,0.0,,,1,True,5.038649731708546,1.2522033525119995,0.8386497317085455, +sidewall-ascend-sprint-mm10-gap4-dy1p0-wo1,sidewall,ascend,sprint,10,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap4-dym1p0-wo1,sidewall,descend,sprint,10,4,-1.0,,,1,True,5.643702898426886,1.2522033525119995,1.4437028984268858, +sidewall-descend-sprint-mm10-gap4-dym2p0-wo1,sidewall,descend,sprint,10,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap5-dy0p0-wo1,sidewall,flat,sprint,10,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap5-dy1p0-wo1,sidewall,ascend,sprint,10,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap5-dym1p0-wo1,sidewall,descend,sprint,10,5,-1.0,,,1,True,5.643702898426886,1.2522033525119995,0.4437028984268858, +sidewall-descend-sprint-mm10-gap5-dym2p0-wo1,sidewall,descend,sprint,10,5,-2.0,,,1,True,6.10893727442744,1.2522033525119995,0.9089372744274398, +sidewall-flat-sprint-mm10-gap6-dy0p0-wo1,sidewall,flat,sprint,10,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap6-dy1p0-wo1,sidewall,ascend,sprint,10,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap6-dym1p0-wo1,sidewall,descend,sprint,10,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap6-dym2p0-wo1,sidewall,descend,sprint,10,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm10-gap7-dy0p0-wo1,sidewall,flat,sprint,10,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm10-gap7-dy1p0-wo1,sidewall,ascend,sprint,10,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap7-dym1p0-wo1,sidewall,descend,sprint,10,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm10-gap7-dym2p0-wo1,sidewall,descend,sprint,10,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap0-dy0p0-wo0,sidewall,flat,sprint,11,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap0-dy1p0-wo0,sidewall,ascend,sprint,11,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap0-dym1p0-wo0,sidewall,descend,sprint,11,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap0-dym2p0-wo0,sidewall,descend,sprint,11,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap1-dy0p0-wo0,sidewall,flat,sprint,11,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap1-dy1p0-wo0,sidewall,ascend,sprint,11,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap1-dym1p0-wo0,sidewall,descend,sprint,11,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap1-dym2p0-wo0,sidewall,descend,sprint,11,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap2-dy0p0-wo0,sidewall,flat,sprint,11,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap2-dy1p0-wo0,sidewall,ascend,sprint,11,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap2-dym1p0-wo0,sidewall,descend,sprint,11,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap2-dym2p0-wo0,sidewall,descend,sprint,11,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap3-dy0p0-wo0,sidewall,flat,sprint,11,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap3-dy1p0-wo0,sidewall,ascend,sprint,11,3,1.0,,,0,True,4.008322460209515,1.2522033525119995,0.8083224602095145, +sidewall-descend-sprint-mm11-gap3-dym1p0-wo0,sidewall,descend,sprint,11,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap3-dym2p0-wo0,sidewall,descend,sprint,11,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap4-dy0p0-wo0,sidewall,flat,sprint,11,4,0.0,,,0,True,5.039846818181262,1.2522033525119995,0.839846818181262, +sidewall-ascend-sprint-mm11-gap4-dy1p0-wo0,sidewall,ascend,sprint,11,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap4-dym1p0-wo0,sidewall,descend,sprint,11,4,-1.0,,,0,True,5.645008970321087,1.2522033525119995,1.445008970321087, +sidewall-descend-sprint-mm11-gap4-dym2p0-wo0,sidewall,descend,sprint,11,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap5-dy0p0-wo0,sidewall,flat,sprint,11,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap5-dy1p0-wo0,sidewall,ascend,sprint,11,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap5-dym1p0-wo0,sidewall,descend,sprint,11,5,-1.0,,,0,True,5.645008970321087,1.2522033525119995,0.4450089703210871, +sidewall-descend-sprint-mm11-gap5-dym2p0-wo0,sidewall,descend,sprint,11,5,-2.0,,,0,True,6.1103174762637655,1.2522033525119995,0.9103174762637654, +sidewall-flat-sprint-mm11-gap6-dy0p0-wo0,sidewall,flat,sprint,11,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap6-dy1p0-wo0,sidewall,ascend,sprint,11,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap6-dym1p0-wo0,sidewall,descend,sprint,11,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap6-dym2p0-wo0,sidewall,descend,sprint,11,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap7-dy0p0-wo0,sidewall,flat,sprint,11,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap7-dy1p0-wo0,sidewall,ascend,sprint,11,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap7-dym1p0-wo0,sidewall,descend,sprint,11,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap7-dym2p0-wo0,sidewall,descend,sprint,11,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap0-dy0p0-wo1,sidewall,flat,sprint,11,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap0-dy1p0-wo1,sidewall,ascend,sprint,11,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap0-dym1p0-wo1,sidewall,descend,sprint,11,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap0-dym2p0-wo1,sidewall,descend,sprint,11,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap1-dy0p0-wo1,sidewall,flat,sprint,11,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap1-dy1p0-wo1,sidewall,ascend,sprint,11,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap1-dym1p0-wo1,sidewall,descend,sprint,11,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap1-dym2p0-wo1,sidewall,descend,sprint,11,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap2-dy0p0-wo1,sidewall,flat,sprint,11,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap2-dy1p0-wo1,sidewall,ascend,sprint,11,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap2-dym1p0-wo1,sidewall,descend,sprint,11,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap2-dym2p0-wo1,sidewall,descend,sprint,11,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap3-dy0p0-wo1,sidewall,flat,sprint,11,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap3-dy1p0-wo1,sidewall,ascend,sprint,11,3,1.0,,,1,True,4.008322460209515,1.2522033525119995,0.8083224602095145, +sidewall-descend-sprint-mm11-gap3-dym1p0-wo1,sidewall,descend,sprint,11,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap3-dym2p0-wo1,sidewall,descend,sprint,11,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap4-dy0p0-wo1,sidewall,flat,sprint,11,4,0.0,,,1,True,5.039846818181262,1.2522033525119995,0.839846818181262, +sidewall-ascend-sprint-mm11-gap4-dy1p0-wo1,sidewall,ascend,sprint,11,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap4-dym1p0-wo1,sidewall,descend,sprint,11,4,-1.0,,,1,True,5.645008970321087,1.2522033525119995,1.445008970321087, +sidewall-descend-sprint-mm11-gap4-dym2p0-wo1,sidewall,descend,sprint,11,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap5-dy0p0-wo1,sidewall,flat,sprint,11,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap5-dy1p0-wo1,sidewall,ascend,sprint,11,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap5-dym1p0-wo1,sidewall,descend,sprint,11,5,-1.0,,,1,True,5.645008970321087,1.2522033525119995,0.4450089703210871, +sidewall-descend-sprint-mm11-gap5-dym2p0-wo1,sidewall,descend,sprint,11,5,-2.0,,,1,True,6.1103174762637655,1.2522033525119995,0.9103174762637654, +sidewall-flat-sprint-mm11-gap6-dy0p0-wo1,sidewall,flat,sprint,11,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap6-dy1p0-wo1,sidewall,ascend,sprint,11,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap6-dym1p0-wo1,sidewall,descend,sprint,11,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap6-dym2p0-wo1,sidewall,descend,sprint,11,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm11-gap7-dy0p0-wo1,sidewall,flat,sprint,11,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm11-gap7-dy1p0-wo1,sidewall,ascend,sprint,11,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap7-dym1p0-wo1,sidewall,descend,sprint,11,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm11-gap7-dym2p0-wo1,sidewall,descend,sprint,11,7,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap0-dy0p0-wo0,sidewall,flat,sprint,12,0,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap0-dy1p0-wo0,sidewall,ascend,sprint,12,0,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap0-dym1p0-wo0,sidewall,descend,sprint,12,0,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap0-dym2p0-wo0,sidewall,descend,sprint,12,0,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap1-dy0p0-wo0,sidewall,flat,sprint,12,1,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap1-dy1p0-wo0,sidewall,ascend,sprint,12,1,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap1-dym1p0-wo0,sidewall,descend,sprint,12,1,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap1-dym2p0-wo0,sidewall,descend,sprint,12,1,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap2-dy0p0-wo0,sidewall,flat,sprint,12,2,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap2-dy1p0-wo0,sidewall,ascend,sprint,12,2,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap2-dym1p0-wo0,sidewall,descend,sprint,12,2,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap2-dym2p0-wo0,sidewall,descend,sprint,12,2,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap3-dy0p0-wo0,sidewall,flat,sprint,12,3,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap3-dy1p0-wo0,sidewall,ascend,sprint,12,3,1.0,,,0,True,4.008856160638927,1.2522033525119995,0.8088561606389266, +sidewall-descend-sprint-mm12-gap3-dym1p0-wo0,sidewall,descend,sprint,12,3,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap3-dym2p0-wo0,sidewall,descend,sprint,12,3,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap4-dy0p0-wo0,sidewall,flat,sprint,12,4,0.0,,,0,True,5.040500427395363,1.2522033525119995,0.8405004273953631, +sidewall-ascend-sprint-mm12-gap4-dy1p0-wo0,sidewall,ascend,sprint,12,4,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap4-dym1p0-wo0,sidewall,descend,sprint,12,4,-1.0,,,0,True,5.645722085575318,1.2522033525119995,1.4457220855753175, +sidewall-descend-sprint-mm12-gap4-dym2p0-wo0,sidewall,descend,sprint,12,4,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap5-dy0p0-wo0,sidewall,flat,sprint,12,5,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap5-dy1p0-wo0,sidewall,ascend,sprint,12,5,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap5-dym1p0-wo0,sidewall,descend,sprint,12,5,-1.0,,,0,True,5.645722085575318,1.2522033525119995,0.44572208557531745, +sidewall-descend-sprint-mm12-gap5-dym2p0-wo0,sidewall,descend,sprint,12,5,-2.0,,,0,True,6.111071066466395,1.2522033525119995,0.9110710664663948, +sidewall-flat-sprint-mm12-gap6-dy0p0-wo0,sidewall,flat,sprint,12,6,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap6-dy1p0-wo0,sidewall,ascend,sprint,12,6,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap6-dym1p0-wo0,sidewall,descend,sprint,12,6,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap6-dym2p0-wo0,sidewall,descend,sprint,12,6,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap7-dy0p0-wo0,sidewall,flat,sprint,12,7,0.0,,,0,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap7-dy1p0-wo0,sidewall,ascend,sprint,12,7,1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap7-dym1p0-wo0,sidewall,descend,sprint,12,7,-1.0,,,0,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap7-dym2p0-wo0,sidewall,descend,sprint,12,7,-2.0,,,0,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap0-dy0p0-wo1,sidewall,flat,sprint,12,0,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap0-dy1p0-wo1,sidewall,ascend,sprint,12,0,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap0-dym1p0-wo1,sidewall,descend,sprint,12,0,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap0-dym2p0-wo1,sidewall,descend,sprint,12,0,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap1-dy0p0-wo1,sidewall,flat,sprint,12,1,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap1-dy1p0-wo1,sidewall,ascend,sprint,12,1,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap1-dym1p0-wo1,sidewall,descend,sprint,12,1,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap1-dym2p0-wo1,sidewall,descend,sprint,12,1,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap2-dy0p0-wo1,sidewall,flat,sprint,12,2,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap2-dy1p0-wo1,sidewall,ascend,sprint,12,2,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap2-dym1p0-wo1,sidewall,descend,sprint,12,2,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap2-dym2p0-wo1,sidewall,descend,sprint,12,2,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap3-dy0p0-wo1,sidewall,flat,sprint,12,3,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap3-dy1p0-wo1,sidewall,ascend,sprint,12,3,1.0,,,1,True,4.008856160638927,1.2522033525119995,0.8088561606389266, +sidewall-descend-sprint-mm12-gap3-dym1p0-wo1,sidewall,descend,sprint,12,3,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap3-dym2p0-wo1,sidewall,descend,sprint,12,3,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap4-dy0p0-wo1,sidewall,flat,sprint,12,4,0.0,,,1,True,5.040500427395363,1.2522033525119995,0.8405004273953631, +sidewall-ascend-sprint-mm12-gap4-dy1p0-wo1,sidewall,ascend,sprint,12,4,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap4-dym1p0-wo1,sidewall,descend,sprint,12,4,-1.0,,,1,True,5.645722085575318,1.2522033525119995,1.4457220855753175, +sidewall-descend-sprint-mm12-gap4-dym2p0-wo1,sidewall,descend,sprint,12,4,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap5-dy0p0-wo1,sidewall,flat,sprint,12,5,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap5-dy1p0-wo1,sidewall,ascend,sprint,12,5,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap5-dym1p0-wo1,sidewall,descend,sprint,12,5,-1.0,,,1,True,5.645722085575318,1.2522033525119995,0.44572208557531745, +sidewall-descend-sprint-mm12-gap5-dym2p0-wo1,sidewall,descend,sprint,12,5,-2.0,,,1,True,6.111071066466395,1.2522033525119995,0.9110710664663948, +sidewall-flat-sprint-mm12-gap6-dy0p0-wo1,sidewall,flat,sprint,12,6,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap6-dy1p0-wo1,sidewall,ascend,sprint,12,6,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap6-dym1p0-wo1,sidewall,descend,sprint,12,6,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap6-dym2p0-wo1,sidewall,descend,sprint,12,6,-2.0,,,1,False,,1.2522033525119995,, +sidewall-flat-sprint-mm12-gap7-dy0p0-wo1,sidewall,flat,sprint,12,7,0.0,,,1,False,,1.2522033525119995,, +sidewall-ascend-sprint-mm12-gap7-dy1p0-wo1,sidewall,ascend,sprint,12,7,1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap7-dym1p0-wo1,sidewall,descend,sprint,12,7,-1.0,,,1,False,,1.2522033525119995,, +sidewall-descend-sprint-mm12-gap7-dym2p0-wo1,sidewall,descend,sprint,12,7,-2.0,,,1,False,,1.2522033525119995,, diff --git a/tools/pathing_data/theory-matrix.json b/tools/pathing_data/theory-matrix.json new file mode 100644 index 0000000000..68be2ae495 --- /dev/null +++ b/tools/pathing_data/theory-matrix.json @@ -0,0 +1,48622 @@ +[ + { + "case_id": "linear-flat-walk-mm0-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": 1.317113405646343, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.94476992399465, + "apex_y": 1.2522033525119995, + "margin": 0.7447699239946501, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": 0.3171134056463427, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8958406866118747, + "apex_y": 1.2522033525119995, + "margin": 0.6958406866118745, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.207960450907294, + "apex_y": 1.2522033525119995, + "margin": 1.007960450907294, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.207960450907294, + "apex_y": 1.2522033525119995, + "margin": 0.007960450907293914, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm0-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm0-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm0-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.366298502068217, + "apex_y": 1.2522033525119995, + "margin": 1.1662985020682168, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.0333486332242363, + "apex_y": 1.2522033525119995, + "margin": 0.8333486332242361, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.366298502068217, + "apex_y": 1.2522033525119995, + "margin": 0.1662985020682166, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.459075120299828, + "apex_y": 1.2522033525119995, + "margin": 1.259075120299828, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.459075120299828, + "apex_y": 1.2522033525119995, + "margin": 0.25907512029982804, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.8031629073028737, + "apex_y": 1.2522033525119995, + "margin": 0.6031629073028735, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm1-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm1-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm1-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.596453105696384, + "apex_y": 1.2522033525119995, + "margin": 1.396453105696384, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": 1.1152130674817657, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.596453105696384, + "apex_y": 1.2522033525119995, + "margin": 0.3964531056963838, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.7666011210934505, + "apex_y": 1.2522033525119995, + "margin": 1.5666011210934503, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": 0.11521306748176574, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.7666011210934505, + "apex_y": 1.2522033525119995, + "margin": 0.5666011210934503, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.12814344849486, + "apex_y": 1.2522033525119995, + "margin": 0.9281434484948594, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm2-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm2-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm2-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.7221175192773623, + "apex_y": 1.2522033525119995, + "margin": 1.5221175192773624, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": 1.269111048586376, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.7221175192773623, + "apex_y": 1.2522033525119995, + "margin": 0.5221175192773622, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": 0.2691110485863759, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.9345103175267675, + "apex_y": 1.2522033525119995, + "margin": 0.7345103175267673, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.3055828239856835, + "apex_y": 1.2522033525119995, + "margin": 1.1055828239856833, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.3055828239856835, + "apex_y": 1.2522033525119995, + "margin": 0.1055828239856833, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm3-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm3-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm3-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.790730289092578, + "apex_y": 1.2522033525119995, + "margin": 1.5907302890925779, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": 1.353139346269494, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.790730289092578, + "apex_y": 1.2522033525119995, + "margin": 0.5907302890925776, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": 0.35313934626949406, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.02618873877936, + "apex_y": 1.2522033525119995, + "margin": 0.8261887387793596, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.402464723003673, + "apex_y": 1.2522033525119995, + "margin": 1.202464723003673, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.402464723003673, + "apex_y": 1.2522033525119995, + "margin": 0.2024647230036729, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm4-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm4-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm4-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": 1.399018796804477, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.828192861411685, + "apex_y": 1.2522033525119995, + "margin": 0.628192861411685, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": 0.39901879680447694, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.076245156783275, + "apex_y": 1.2522033525119995, + "margin": 0.8762451567832752, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4553622398674975, + "apex_y": 1.2522033525119995, + "margin": 1.2553622398674973, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4553622398674975, + "apex_y": 1.2522033525119995, + "margin": 0.2553622398674973, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm5-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm5-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm5-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": 1.4240689767965766, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.848647425897918, + "apex_y": 1.2522033525119995, + "margin": 0.6486474258979178, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": 0.4240689767965766, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.103575961013412, + "apex_y": 1.2522033525119995, + "margin": 0.903575961013412, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.484244284075144, + "apex_y": 1.2522033525119995, + "margin": 1.284244284075144, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.484244284075144, + "apex_y": 1.2522033525119995, + "margin": 0.284244284075144, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm6-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm6-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm6-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": 1.4377463750722632, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8598156181074006, + "apex_y": 1.2522033525119995, + "margin": 0.6598156181074004, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": 0.4377463750722632, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.118498580123067, + "apex_y": 1.2522033525119995, + "margin": 0.9184985801230665, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.500013880212519, + "apex_y": 1.2522033525119995, + "margin": 1.3000138802125187, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.500013880212519, + "apex_y": 1.2522033525119995, + "margin": 0.30001388021251874, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm7-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm7-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm7-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": 1.4452142345307886, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.865913451053779, + "apex_y": 1.2522033525119995, + "margin": 0.665913451053779, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": 0.44521423453078857, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.126646330156939, + "apex_y": 1.2522033525119995, + "margin": 0.926646330156939, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.508624079703527, + "apex_y": 1.2522033525119995, + "margin": 1.3086240797035265, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.508624079703527, + "apex_y": 1.2522033525119995, + "margin": 0.30862407970352645, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm8-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm8-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm8-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": 1.4492916857951426, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8692428678425004, + "apex_y": 1.2522033525119995, + "margin": 0.6692428678425002, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": 0.44929168579514256, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.131095001675432, + "apex_y": 1.2522033525119995, + "margin": 0.9310950016754322, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.513325248625614, + "apex_y": 1.2522033525119995, + "margin": 1.3133252486256142, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.513325248625614, + "apex_y": 1.2522033525119995, + "margin": 0.31332524862561417, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm9-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm9-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm9-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": 1.4515179741854807, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8710607294091433, + "apex_y": 1.2522033525119995, + "margin": 0.6710607294091431, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": 0.4515179741854807, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.13352397632453, + "apex_y": 1.2522033525119995, + "margin": 0.9335239763245298, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.515892086857076, + "apex_y": 1.2522033525119995, + "margin": 1.315892086857076, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.515892086857076, + "apex_y": 1.2522033525119995, + "margin": 0.31589208685707604, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm10-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm10-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm10-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": 1.452733527646605, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8720532818245297, + "apex_y": 1.2522033525119995, + "margin": 0.6720532818245295, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": 0.4527335276466049, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.1348501964829385, + "apex_y": 1.2522033525119995, + "margin": 0.9348501964829383, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.517293580531455, + "apex_y": 1.2522033525119995, + "margin": 1.3172935805314552, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.517293580531455, + "apex_y": 1.2522033525119995, + "margin": 0.3172935805314552, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm11-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm11-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm11-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": 1.4533972198363783, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8725952154433307, + "apex_y": 1.2522033525119995, + "margin": 0.6725952154433306, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": 0.45339721983637826, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.135574312689427, + "apex_y": 1.2522033525119995, + "margin": 0.9355743126894271, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.518058796077664, + "apex_y": 1.2522033525119995, + "margin": 1.318058796077664, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.518058796077664, + "apex_y": 1.2522033525119995, + "margin": 0.31805879607766396, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-walk-mm12-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-walk-mm12-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-walk-mm12-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.132075308966179, + "apex_y": 1.2522033525119995, + "margin": 0.9320753089661786, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": 0.7711758286886656, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4822841946066925, + "apex_y": 1.2522033525119995, + "margin": 1.2822841946066923, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4822841946066925, + "apex_y": 1.2522033525119995, + "margin": 0.2822841946066923, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.884447214524587, + "apex_y": 1.2522033525119995, + "margin": 0.684447214524587, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm0-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm0-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm0-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.553603887039745, + "apex_y": 1.2522033525119995, + "margin": 1.3536038870397449, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 1.2874110562665573, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.553603887039745, + "apex_y": 1.2522033525119995, + "margin": 0.3536038870397449, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 0.2874110562665573, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.045518628294644, + "apex_y": 1.2522033525119995, + "margin": 0.8455186282946441, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.479649670920165, + "apex_y": 1.2522033525119995, + "margin": 1.2796496709201648, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.479649670920165, + "apex_y": 1.2522033525119995, + "margin": 0.2796496709201648, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm1-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm1-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm1-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.783758490667913, + "apex_y": 1.2522033525119995, + "margin": 1.583758490667913, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 1.569275490524089, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.783758490667913, + "apex_y": 1.2522033525119995, + "margin": 0.583758490667913, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 0.5692754905240891, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.353044629088269, + "apex_y": 1.2522033525119995, + "margin": 1.153044629088269, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.353044629088269, + "apex_y": 1.2522033525119995, + "margin": 0.153044629088269, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.804630212112152, + "apex_y": 1.2522033525119995, + "margin": 0.6046302121121521, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm2-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm2-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm2-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.909422904248892, + "apex_y": 1.2522033525119995, + "margin": 0.7094229042488918, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 0.7231734716286997, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.520953825521586, + "apex_y": 1.2522033525119995, + "margin": 1.3209538255215856, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.520953825521586, + "apex_y": 1.2522033525119995, + "margin": 0.3209538255215856, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.982069587602977, + "apex_y": 1.2522033525119995, + "margin": 0.7820695876029768, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm3-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm3-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm3-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.978035674064107, + "apex_y": 1.2522033525119995, + "margin": 0.7780356740641068, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 0.807201769311817, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.612632246774177, + "apex_y": 1.2522033525119995, + "margin": 1.4126322467741765, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.612632246774177, + "apex_y": 1.2522033525119995, + "margin": 0.41263224677417654, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.078951486620966, + "apex_y": 1.2522033525119995, + "margin": 0.8789514866209656, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm4-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm4-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm4-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.015498246383214, + "apex_y": 1.2522033525119995, + "margin": 0.8154982463832141, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 0.8530812198467999, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.662688664778092, + "apex_y": 1.2522033525119995, + "margin": 1.4626886647780921, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.662688664778092, + "apex_y": 1.2522033525119995, + "margin": 0.4626886647780921, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.131849003484789, + "apex_y": 1.2522033525119995, + "margin": 0.931849003484789, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm5-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm5-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm5-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.035952810869447, + "apex_y": 1.2522033525119995, + "margin": 0.835952810869447, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 0.8781313998389004, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.69001946900823, + "apex_y": 1.2522033525119995, + "margin": 1.4900194690082298, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.69001946900823, + "apex_y": 1.2522033525119995, + "margin": 0.4900194690082298, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.160731047692436, + "apex_y": 1.2522033525119995, + "margin": 0.9607310476924358, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm6-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm6-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm6-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.04712100307893, + "apex_y": 1.2522033525119995, + "margin": 0.8471210030789296, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 0.8918087981145861, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.704942088117885, + "apex_y": 1.2522033525119995, + "margin": 1.5049420881178852, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.704942088117885, + "apex_y": 1.2522033525119995, + "margin": 0.5049420881178852, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.176500643829812, + "apex_y": 1.2522033525119995, + "margin": 0.9765006438298114, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm7-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm7-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm7-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.053218836025307, + "apex_y": 1.2522033525119995, + "margin": 0.8532188360253068, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 0.8992766575731119, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.713089838151757, + "apex_y": 1.2522033525119995, + "margin": 1.5130898381517568, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.713089838151757, + "apex_y": 1.2522033525119995, + "margin": 0.5130898381517568, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.185110843320819, + "apex_y": 1.2522033525119995, + "margin": 0.9851108433208191, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm8-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm8-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm8-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.0565482528140295, + "apex_y": 1.2522033525119995, + "margin": 0.8565482528140294, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 0.9033541088374646, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.717538509670249, + "apex_y": 1.2522033525119995, + "margin": 1.5175385096702492, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.717538509670249, + "apex_y": 1.2522033525119995, + "margin": 0.5175385096702492, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.189812012242907, + "apex_y": 1.2522033525119995, + "margin": 0.9898120122429068, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm9-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm9-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm9-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.0583661143806715, + "apex_y": 1.2522033525119995, + "margin": 0.8583661143806713, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 0.9055803972278049, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.719967484319349, + "apex_y": 1.2522033525119995, + "margin": 1.5199674843193485, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.719967484319349, + "apex_y": 1.2522033525119995, + "margin": 0.5199674843193485, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.192378850474369, + "apex_y": 1.2522033525119995, + "margin": 0.9923788504743687, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm10-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm10-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm10-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.059358666796059, + "apex_y": 1.2522033525119995, + "margin": 0.8593586667960587, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 0.9067959506889283, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.721293704477756, + "apex_y": 1.2522033525119995, + "margin": 1.5212937044777561, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.721293704477756, + "apex_y": 1.2522033525119995, + "margin": 0.5212937044777561, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.193780344148747, + "apex_y": 1.2522033525119995, + "margin": 0.9937803441487469, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm11-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm11-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm11-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap0-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap0-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap0-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap0-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap1-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap1-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap1-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap1-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap2-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap2-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap2-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap2-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap3-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap3-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.05990060041486, + "apex_y": 1.2522033525119995, + "margin": 0.8599006004148597, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap3-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap3-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap4-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 0.9074596428787016, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap4-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap4-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.722017820684245, + "apex_y": 1.2522033525119995, + "margin": 1.522017820684245, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap4-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap5-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap5-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap5-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.722017820684245, + "apex_y": 1.2522033525119995, + "margin": 0.522017820684245, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap5-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 6.194545559694956, + "apex_y": 1.2522033525119995, + "margin": 0.9945455596949557, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap6-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap6-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap6-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap6-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-flat-sprint-mm12-gap7-dy0p0", + "family": "linear", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-ascend-sprint-mm12-gap7-dy1p0", + "family": "linear", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap7-dym1p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "linear-descend-sprint-mm12-gap7-dym2p0", + "family": "linear", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm0-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": 0.9171134056463428, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm0-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": -0.0828865943536572, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm0-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": -1.0828865943536572, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm0-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 2.517113405646343, + "apex_y": 1.2522033525119995, + "margin": -2.0828865943536568, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm1-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.0333486332242363, + "apex_y": 1.2522033525119995, + "margin": 1.4333486332242362, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm1-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.0333486332242363, + "apex_y": 1.2522033525119995, + "margin": 0.4333486332242362, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm1-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.0333486332242363, + "apex_y": 1.2522033525119995, + "margin": -0.5666513667757638, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm1-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.0333486332242363, + "apex_y": 1.2522033525119995, + "margin": -1.5666513667757633, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm2-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": 1.7152130674817658, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm2-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": 0.7152130674817658, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm2-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": -0.28478693251823417, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm2-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.315213067481766, + "apex_y": 1.2522033525119995, + "margin": -1.2847869325182337, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm3-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": 1.869111048586376, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm3-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": 0.869111048586376, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm3-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": -0.130888951413624, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm3-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.469111048586376, + "apex_y": 1.2522033525119995, + "margin": -1.1308889514136236, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm4-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": 1.9531393462694941, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm4-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": 0.9531393462694941, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm4-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": -0.04686065373050585, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm4-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.5531393462694942, + "apex_y": 1.2522033525119995, + "margin": -1.0468606537305054, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm5-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": 1.999018796804477, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm5-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": 0.999018796804477, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm5-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": -0.0009812031955229727, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm5-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.599018796804477, + "apex_y": 1.2522033525119995, + "margin": -1.0009812031955225, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm6-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": 2.0240689767965767, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm6-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": 1.0240689767965767, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm6-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": 0.024068976796576713, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm6-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.624068976796577, + "apex_y": 1.2522033525119995, + "margin": -0.9759310232034228, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm7-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": 2.0377463750722633, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm7-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": 1.0377463750722633, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm7-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": 0.037746375072263305, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm7-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.6377463750722634, + "apex_y": 1.2522033525119995, + "margin": -0.9622536249277363, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm8-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": 2.0452142345307887, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm8-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": 1.0452142345307887, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm8-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": 0.045214234530788655, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm8-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.6452142345307887, + "apex_y": 1.2522033525119995, + "margin": -0.9547857654692109, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm9-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": 2.0492916857951426, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm9-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": 1.0492916857951426, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm9-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": 0.04929168579514265, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm9-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.6492916857951427, + "apex_y": 1.2522033525119995, + "margin": -0.9507083142048569, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm10-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": 2.0515179741854808, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm10-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": 1.0515179741854808, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm10-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": 0.051517974185480764, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm10-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.651517974185481, + "apex_y": 1.2522033525119995, + "margin": -0.9484820258145188, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm11-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": 2.052733527646605, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm11-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": 1.052733527646605, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm11-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": 0.052733527646604994, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm11-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.652733527646605, + "apex_y": 1.2522033525119995, + "margin": -0.9472664723533946, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm12-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": 2.0533972198363784, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm12-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": 1.0533972198363784, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm12-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": 0.05339721983637835, + "notes": "" + }, + { + "case_id": "neo-neo-walk-mm12-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.6533972198363784, + "apex_y": 1.2522033525119995, + "margin": -0.9466027801636212, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm0-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": 2.3711758286886657, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm0-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": 1.3711758286886657, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm0-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": 0.3711758286886657, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm0-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": -0.6288241713113338, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm1-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 2.8874110562665574, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm1-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 1.8874110562665574, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm1-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 0.8874110562665574, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm1-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": false, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": -0.11258894373344219, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm2-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 3.169275490524089, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm2-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 2.169275490524089, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm2-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 1.1692754905240892, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm2-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 0.16927549052408963, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm3-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 3.3231734716287, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm3-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 2.3231734716287, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm3-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 1.3231734716286998, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm3-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 0.32317347162870025, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm4-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 3.407201769311817, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm4-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 2.407201769311817, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm4-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 1.407201769311817, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm4-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 0.4072017693118175, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm5-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 3.4530812198468, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm5-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 2.4530812198468, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm5-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 1.4530812198468, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm5-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 0.4530812198468004, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm6-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 3.4781313998389005, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm6-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 2.4781313998389005, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm6-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 1.4781313998389005, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm6-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 0.47813139983890096, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm7-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 3.491808798114586, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm7-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 2.491808798114586, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm7-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 1.4918087981145862, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm7-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 0.49180879811458666, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm8-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 3.499276657573112, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm8-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 2.499276657573112, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm8-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 1.499276657573112, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm8-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 0.49927665757311246, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm9-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 3.5033541088374647, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm9-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 2.5033541088374647, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm9-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 1.5033541088374647, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm9-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 0.5033541088374651, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm10-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 3.505580397227805, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm10-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 2.505580397227805, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm10-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 1.505580397227805, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm10-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 0.5055803972278055, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm11-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 3.5067959506889284, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm11-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 2.5067959506889284, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm11-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 1.5067959506889284, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm11-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 0.5067959506889288, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm12-wall1", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 1, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 3.5074596428787017, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm12-wall2", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 2, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 2.5074596428787017, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm12-wall3", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 3, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 1.5074596428787017, + "notes": "" + }, + { + "case_id": "neo-neo-sprint-mm12-wall4", + "family": "neo", + "subfamily": "neo", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": null, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": 4, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 0.5074596428787022, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.971175828688666, + "apex_y": 1.2522033525119995, + "margin": 0.7711758286886656, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.671522403140402, + "apex_y": 1.2, + "margin": 1.4715224031404017, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.671522403140402, + "apex_y": 1.2, + "margin": 0.4715224031404017, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.4809562314414415, + "apex_y": 0.7, + "margin": 1.2809562314414416, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.4809562314414415, + "apex_y": 0.7, + "margin": 0.28095623144144133, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.593413404024811, + "apex_y": 0.19999999999999996, + "margin": 0.39341340402481095, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm0-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 1.2874110562665573, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.4874110562665575, + "apex_y": 1.2522033525119995, + "margin": 0.2874110562665573, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.1566372864603425, + "apex_y": 1.2, + "margin": 0.9566372864603423, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.8119173623355342, + "apex_y": 0.7, + "margin": 0.6119173623355341, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.7750953286829638, + "apex_y": 0.19999999999999996, + "margin": 0.5750953286829639, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm1-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 1.569275490524089, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.769275490524089, + "apex_y": 1.2522033525119995, + "margin": 0.5692754905240891, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.421510012753032, + "apex_y": 1.2, + "margin": 1.2215100127530318, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.421510012753032, + "apex_y": 1.2, + "margin": 0.22151001275303184, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 2.9926221398037094, + "apex_y": 0.7, + "margin": 0.7926221398037092, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.874293659546315, + "apex_y": 0.19999999999999996, + "margin": 0.6742936595463151, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2067611897704629, + "apex_y": 0.012499999999999956, + "margin": 0.0067611897704629165, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm2-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.9231734716287, + "apex_y": 1.2522033525119995, + "margin": 0.7231734716286997, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.56613052130884, + "apex_y": 1.2, + "margin": 1.3661305213088397, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.56613052130884, + "apex_y": 1.2, + "margin": 0.3661305213088397, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.091286948301333, + "apex_y": 0.7, + "margin": 0.891286948301333, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.928455948197705, + "apex_y": 0.19999999999999996, + "margin": 0.7284559481977051, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.231000442014838, + "apex_y": 0.012499999999999956, + "margin": 0.03100044201483798, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm3-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.007201769311817, + "apex_y": 1.2522033525119995, + "margin": 0.807201769311817, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.645093318980311, + "apex_y": 1.2, + "margin": 1.4450933189803106, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.645093318980311, + "apex_y": 1.2, + "margin": 0.44509331898031057, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.145157933741036, + "apex_y": 0.7, + "margin": 0.9451579337410356, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9580285578013639, + "apex_y": 0.19999999999999996, + "margin": 0.7580285578013639, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2442350737402668, + "apex_y": 0.012499999999999956, + "margin": 0.044235073740266806, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm4-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.0530812198468, + "apex_y": 1.2522033525119995, + "margin": 0.8530812198467999, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.688207006508934, + "apex_y": 1.2, + "margin": 1.488207006508934, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.688207006508934, + "apex_y": 1.2, + "margin": 0.4882070065089339, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.1745714917911134, + "apex_y": 0.7, + "margin": 0.9745714917911132, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9741752026449617, + "apex_y": 0.19999999999999996, + "margin": 0.7741752026449618, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2514611826623507, + "apex_y": 0.012499999999999956, + "margin": 0.05146118266235078, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm5-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.078131399838901, + "apex_y": 1.2522033525119995, + "margin": 0.8781313998389004, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.711747079899563, + "apex_y": 1.2, + "margin": 1.5117470798995631, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.711747079899563, + "apex_y": 1.2, + "margin": 0.5117470798995631, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.1906312944864554, + "apex_y": 0.7, + "margin": 0.9906312944864553, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.982991270729566, + "apex_y": 0.19999999999999996, + "margin": 0.7829912707295661, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2554066381338087, + "apex_y": 0.012499999999999956, + "margin": 0.05540663813380875, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm6-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.091808798114586, + "apex_y": 1.2522033525119995, + "margin": 0.8918087981145861, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.7245999599708455, + "apex_y": 1.2, + "margin": 1.5245999599708453, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.7245999599708455, + "apex_y": 1.2, + "margin": 0.5245999599708453, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.1993999467581125, + "apex_y": 0.7, + "margin": 0.9993999467581123, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9878048439037599, + "apex_y": 0.19999999999999996, + "margin": 0.7878048439037599, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2575608568212246, + "apex_y": 0.012499999999999956, + "margin": 0.05756085682122469, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm7-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.099276657573112, + "apex_y": 1.2522033525119995, + "margin": 0.8992766575731119, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.731617632489766, + "apex_y": 1.2, + "margin": 1.5316176324897661, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.731617632489766, + "apex_y": 1.2, + "margin": 0.5316176324897661, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2041876308984367, + "apex_y": 0.7, + "margin": 1.0041876308984365, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2041876308984367, + "apex_y": 0.7, + "margin": 0.004187630898436545, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9904330548568696, + "apex_y": 0.19999999999999996, + "margin": 0.7904330548568697, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2587370602245538, + "apex_y": 0.012499999999999956, + "margin": 0.05873706022455383, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm8-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.103354108837465, + "apex_y": 1.2522033525119995, + "margin": 0.9033541088374646, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.735449281685096, + "apex_y": 1.2, + "margin": 1.5354492816850955, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.735449281685096, + "apex_y": 1.2, + "margin": 0.5354492816850955, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2068017064390544, + "apex_y": 0.7, + "margin": 1.0068017064390542, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2068017064390544, + "apex_y": 0.7, + "margin": 0.0068017064390542, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9918680580372676, + "apex_y": 0.19999999999999996, + "margin": 0.7918680580372677, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2593792672827715, + "apex_y": 0.012499999999999956, + "margin": 0.05937926728277154, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm9-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.105580397227805, + "apex_y": 1.2522033525119995, + "margin": 0.9055803972278049, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.737541362145748, + "apex_y": 1.2, + "margin": 1.537541362145748, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.737541362145748, + "apex_y": 1.2, + "margin": 0.5375413621457481, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2082289916842317, + "apex_y": 0.7, + "margin": 1.0082289916842315, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2082289916842317, + "apex_y": 0.7, + "margin": 0.008228991684231524, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9926515697737652, + "apex_y": 0.19999999999999996, + "margin": 0.7926515697737653, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2597299123365584, + "apex_y": 0.012499999999999956, + "margin": 0.059729912336558444, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm10-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.106795950688928, + "apex_y": 1.2522033525119995, + "margin": 0.9067959506889283, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.738683638077263, + "apex_y": 1.2, + "margin": 1.5386836380772628, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.738683638077263, + "apex_y": 1.2, + "margin": 0.5386836380772628, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2090082894280987, + "apex_y": 0.7, + "margin": 1.0090082894280985, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.2090082894280987, + "apex_y": 0.7, + "margin": 0.009008289428098504, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.993079367181893, + "apex_y": 0.19999999999999996, + "margin": 0.793079367181893, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2599213645359262, + "apex_y": 0.012499999999999956, + "margin": 0.059921364535926225, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm11-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap2-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap4-ceil4p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 4.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 5.107459642878702, + "apex_y": 1.2522033525119995, + "margin": 0.9074596428787016, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap2-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.73930732073587, + "apex_y": 1.2, + "margin": 1.5393073207358698, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap4-ceil3p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 3.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 4.73930732073587, + "apex_y": 1.2, + "margin": 0.5393073207358698, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap2-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.209433785996249, + "apex_y": 0.7, + "margin": 1.009433785996249, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 3.209433785996249, + "apex_y": 0.7, + "margin": 0.009433785996249, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap4-ceil2p5", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.5, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.7, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.9933129445667304, + "apex_y": 0.19999999999999996, + "margin": 0.7933129445667304, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap2-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap4-ceil2p0", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 2.0, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.19999999999999996, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap1-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": true, + "landing_x": 1.2600258974367808, + "apex_y": 0.012499999999999956, + "margin": 0.060025897436780884, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap2-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap3-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "ceiling-headhitter-sprint-mm12-gap4-ceil1p8125", + "family": "ceiling", + "subfamily": "headhitter", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": 1.8125, + "wall_width": null, + "wall_offset": null, + "expected_reachable": false, + "landing_x": null, + "apex_y": 0.012499999999999956, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.489507369982936, + "apex_y": 1.2522033525119995, + "margin": 1.289507369982936, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 1.9258590718663482, + "apex_y": 1.2522033525119995, + "margin": 0.7258590718663482, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.489507369982936, + "apex_y": 1.2522033525119995, + "margin": 0.2895073699829358, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.8624809325550244, + "apex_y": 1.2522033525119995, + "margin": 0.6624809325550243, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.169858896301496, + "apex_y": 1.2522033525119995, + "margin": 0.969858896301496, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": 3.2045233782724227, + "apex_y": 1.2522033525119995, + "margin": 0.004523378272422551, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.489507369982936, + "apex_y": 1.2522033525119995, + "margin": 1.289507369982936, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 1.9258590718663482, + "apex_y": 1.2522033525119995, + "margin": 0.7258590718663482, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.489507369982936, + "apex_y": 1.2522033525119995, + "margin": 0.2895073699829358, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.8624809325550244, + "apex_y": 1.2522033525119995, + "margin": 0.6624809325550243, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.169858896301496, + "apex_y": 1.2522033525119995, + "margin": 0.969858896301496, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": 3.2045233782724227, + "apex_y": 1.2522033525119995, + "margin": 0.004523378272422551, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm0-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm0-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm0-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.340983683669408, + "apex_y": 1.2522033525119995, + "margin": 1.1409836836694078, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.9978998244796653, + "apex_y": 1.2522033525119995, + "margin": 0.7978998244796651, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.340983683669408, + "apex_y": 1.2522033525119995, + "margin": 0.1409836836694076, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.41715856961436, + "apex_y": 1.2522033525119995, + "margin": 1.2171585696143596, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.41715856961436, + "apex_y": 1.2522033525119995, + "margin": 0.21715856961435964, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.756018889971772, + "apex_y": 1.2522033525119995, + "margin": 0.5560188899717717, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.340983683669408, + "apex_y": 1.2522033525119995, + "margin": 1.1409836836694078, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.9978998244796653, + "apex_y": 1.2522033525119995, + "margin": 0.7978998244796651, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.340983683669408, + "apex_y": 1.2522033525119995, + "margin": 0.1409836836694076, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.41715856961436, + "apex_y": 1.2522033525119995, + "margin": 1.2171585696143596, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.41715856961436, + "apex_y": 1.2522033525119995, + "margin": 0.21715856961435964, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.756018889971772, + "apex_y": 1.2522033525119995, + "margin": 0.5560188899717717, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm1-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm1-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm1-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.567641721713879, + "apex_y": 1.2522033525119995, + "margin": 1.367641721713879, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.2754821046348814, + "apex_y": 1.2522033525119995, + "margin": 1.0754821046348813, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.567641721713879, + "apex_y": 1.2522033525119995, + "margin": 0.36764172171387877, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.720012559448759, + "apex_y": 1.2522033525119995, + "margin": 1.5200125594487588, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.2754821046348814, + "apex_y": 1.2522033525119995, + "margin": 0.07548210463488125, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.720012559448759, + "apex_y": 1.2522033525119995, + "margin": 0.5200125594487588, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.076062246515744, + "apex_y": 1.2522033525119995, + "margin": 0.8760622465157439, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.567641721713879, + "apex_y": 1.2522033525119995, + "margin": 1.367641721713879, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.2754821046348814, + "apex_y": 1.2522033525119995, + "margin": 1.0754821046348813, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.567641721713879, + "apex_y": 1.2522033525119995, + "margin": 0.36764172171387877, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.720012559448759, + "apex_y": 1.2522033525119995, + "margin": 1.5200125594487588, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.2754821046348814, + "apex_y": 1.2522033525119995, + "margin": 0.07548210463488125, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.720012559448759, + "apex_y": 1.2522033525119995, + "margin": 0.5200125594487588, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.076062246515744, + "apex_y": 1.2522033525119995, + "margin": 0.8760622465157439, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm2-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm2-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm2-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.6913970104861606, + "apex_y": 1.2522033525119995, + "margin": 1.4913970104861607, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.427042029599629, + "apex_y": 1.2522033525119995, + "margin": 1.227042029599629, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.6913970104861606, + "apex_y": 1.2522033525119995, + "margin": 0.49139701048616047, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.427042029599629, + "apex_y": 1.2522033525119995, + "margin": 0.22704202959962894, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.8853708378983405, + "apex_y": 1.2522033525119995, + "margin": 0.6853708378983403, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.250805919188753, + "apex_y": 1.2522033525119995, + "margin": 1.050805919188753, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.250805919188753, + "apex_y": 1.2522033525119995, + "margin": 0.0508059191887531, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.6913970104861606, + "apex_y": 1.2522033525119995, + "margin": 1.4913970104861607, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.427042029599629, + "apex_y": 1.2522033525119995, + "margin": 1.227042029599629, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.6913970104861606, + "apex_y": 1.2522033525119995, + "margin": 0.49139701048616047, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.427042029599629, + "apex_y": 1.2522033525119995, + "margin": 0.22704202959962894, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.8853708378983405, + "apex_y": 1.2522033525119995, + "margin": 0.6853708378983403, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.250805919188753, + "apex_y": 1.2522033525119995, + "margin": 1.050805919188753, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.250805919188753, + "apex_y": 1.2522033525119995, + "margin": 0.0508059191887531, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm3-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm3-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm3-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.758967398155826, + "apex_y": 1.2522033525119995, + "margin": 1.5589673981558259, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.509793748630381, + "apex_y": 1.2522033525119995, + "margin": 1.3097937486303808, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.758967398155826, + "apex_y": 1.2522033525119995, + "margin": 0.5589673981558256, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.509793748630381, + "apex_y": 1.2522033525119995, + "margin": 0.3097937486303808, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.975656457931811, + "apex_y": 1.2522033525119995, + "margin": 0.7756564579318108, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.346215964468215, + "apex_y": 1.2522033525119995, + "margin": 1.1462159644682144, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.346215964468215, + "apex_y": 1.2522033525119995, + "margin": 0.1462159644682144, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.758967398155826, + "apex_y": 1.2522033525119995, + "margin": 1.5589673981558259, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.509793748630381, + "apex_y": 1.2522033525119995, + "margin": 1.3097937486303808, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.758967398155826, + "apex_y": 1.2522033525119995, + "margin": 0.5589673981558256, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.509793748630381, + "apex_y": 1.2522033525119995, + "margin": 0.3097937486303808, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.975656457931811, + "apex_y": 1.2522033525119995, + "margin": 0.7756564579318108, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.346215964468215, + "apex_y": 1.2522033525119995, + "margin": 1.1462159644682144, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.346215964468215, + "apex_y": 1.2522033525119995, + "margin": 0.1462159644682144, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm4-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm4-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm4-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.554976187221172, + "apex_y": 1.2522033525119995, + "margin": 1.3549761872211716, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.7958608298234635, + "apex_y": 1.2522033525119995, + "margin": 0.5958608298234633, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.554976187221172, + "apex_y": 1.2522033525119995, + "margin": 0.35497618722117164, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.024952406470087, + "apex_y": 1.2522033525119995, + "margin": 0.8249524064700866, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.3983098491908015, + "apex_y": 1.2522033525119995, + "margin": 1.1983098491908013, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.3983098491908015, + "apex_y": 1.2522033525119995, + "margin": 0.1983098491908013, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.554976187221172, + "apex_y": 1.2522033525119995, + "margin": 1.3549761872211716, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.7958608298234635, + "apex_y": 1.2522033525119995, + "margin": 0.5958608298234633, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.554976187221172, + "apex_y": 1.2522033525119995, + "margin": 0.35497618722117164, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.024952406470087, + "apex_y": 1.2522033525119995, + "margin": 0.8249524064700866, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.3983098491908015, + "apex_y": 1.2522033525119995, + "margin": 1.1983098491908013, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.3983098491908015, + "apex_y": 1.2522033525119995, + "margin": 0.1983098491908013, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm5-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm5-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm5-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.579645798691743, + "apex_y": 1.2522033525119995, + "margin": 1.379645798691743, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.8160046435139936, + "apex_y": 1.2522033525119995, + "margin": 0.6160046435139934, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.579645798691743, + "apex_y": 1.2522033525119995, + "margin": 0.379645798691743, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.051867994371985, + "apex_y": 1.2522033525119995, + "margin": 0.8518679943719851, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.426753110249335, + "apex_y": 1.2522033525119995, + "margin": 1.2267531102493345, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.426753110249335, + "apex_y": 1.2522033525119995, + "margin": 0.22675311024933453, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.579645798691743, + "apex_y": 1.2522033525119995, + "margin": 1.379645798691743, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.8160046435139936, + "apex_y": 1.2522033525119995, + "margin": 0.6160046435139934, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.579645798691743, + "apex_y": 1.2522033525119995, + "margin": 0.379645798691743, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.051867994371985, + "apex_y": 1.2522033525119995, + "margin": 0.8518679943719851, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.426753110249335, + "apex_y": 1.2522033525119995, + "margin": 1.2267531102493345, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.426753110249335, + "apex_y": 1.2522033525119995, + "margin": 0.22675311024933453, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm6-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm6-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm6-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.593115406554676, + "apex_y": 1.2522033525119995, + "margin": 1.393115406554676, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.8270031657890233, + "apex_y": 1.2522033525119995, + "margin": 0.6270031657890232, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.593115406554676, + "apex_y": 1.2522033525119995, + "margin": 0.39311540655467603, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.066563905366422, + "apex_y": 1.2522033525119995, + "margin": 0.866563905366422, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.442283130787294, + "apex_y": 1.2522033525119995, + "margin": 1.2422831307872935, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.442283130787294, + "apex_y": 1.2522033525119995, + "margin": 0.24228313078729347, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.593115406554676, + "apex_y": 1.2522033525119995, + "margin": 1.393115406554676, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.8270031657890233, + "apex_y": 1.2522033525119995, + "margin": 0.6270031657890232, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.593115406554676, + "apex_y": 1.2522033525119995, + "margin": 0.39311540655467603, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.066563905366422, + "apex_y": 1.2522033525119995, + "margin": 0.866563905366422, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.442283130787294, + "apex_y": 1.2522033525119995, + "margin": 1.2422831307872935, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.442283130787294, + "apex_y": 1.2522033525119995, + "margin": 0.24228313078729347, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm7-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm7-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm7-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.600469812447837, + "apex_y": 1.2522033525119995, + "margin": 1.4004698124478367, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.833008358951189, + "apex_y": 1.2522033525119995, + "margin": 0.633008358951189, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.600469812447837, + "apex_y": 1.2522033525119995, + "margin": 0.40046981244783675, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.074587872769384, + "apex_y": 1.2522033525119995, + "margin": 0.8745878727693839, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4507625220010185, + "apex_y": 1.2522033525119995, + "margin": 1.2507625220010183, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4507625220010185, + "apex_y": 1.2522033525119995, + "margin": 0.25076252200101834, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.600469812447837, + "apex_y": 1.2522033525119995, + "margin": 1.4004698124478367, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.833008358951189, + "apex_y": 1.2522033525119995, + "margin": 0.633008358951189, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.600469812447837, + "apex_y": 1.2522033525119995, + "margin": 0.40046981244783675, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.074587872769384, + "apex_y": 1.2522033525119995, + "margin": 0.8745878727693839, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4507625220010185, + "apex_y": 1.2522033525119995, + "margin": 1.2507625220010183, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4507625220010185, + "apex_y": 1.2522033525119995, + "margin": 0.25076252200101834, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm8-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm8-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm8-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.604485318065503, + "apex_y": 1.2522033525119995, + "margin": 1.404485318065503, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.836287194417732, + "apex_y": 1.2522033525119995, + "margin": 0.636287194417732, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.604485318065503, + "apex_y": 1.2522033525119995, + "margin": 0.40448531806550303, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.078968958971402, + "apex_y": 1.2522033525119995, + "margin": 0.8789689589714023, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.455392269603713, + "apex_y": 1.2522033525119995, + "margin": 1.2553922696037132, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.455392269603713, + "apex_y": 1.2522033525119995, + "margin": 0.2553922696037132, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.604485318065503, + "apex_y": 1.2522033525119995, + "margin": 1.404485318065503, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.836287194417732, + "apex_y": 1.2522033525119995, + "margin": 0.636287194417732, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.604485318065503, + "apex_y": 1.2522033525119995, + "margin": 0.40448531806550303, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.078968958971402, + "apex_y": 1.2522033525119995, + "margin": 0.8789689589714023, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.455392269603713, + "apex_y": 1.2522033525119995, + "margin": 1.2553922696037132, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.455392269603713, + "apex_y": 1.2522033525119995, + "margin": 0.2553922696037132, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm9-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm9-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm9-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.6066777841327484, + "apex_y": 1.2522033525119995, + "margin": 1.4066777841327482, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.8380774385824643, + "apex_y": 1.2522033525119995, + "margin": 0.6380774385824641, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.6066777841327484, + "apex_y": 1.2522033525119995, + "margin": 0.40667778413274824, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.081361032037703, + "apex_y": 1.2522033525119995, + "margin": 0.8813610320377032, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.457920111794784, + "apex_y": 1.2522033525119995, + "margin": 1.2579201117947836, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.457920111794784, + "apex_y": 1.2522033525119995, + "margin": 0.2579201117947836, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.6066777841327484, + "apex_y": 1.2522033525119995, + "margin": 1.4066777841327482, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.8380774385824643, + "apex_y": 1.2522033525119995, + "margin": 0.6380774385824641, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.6066777841327484, + "apex_y": 1.2522033525119995, + "margin": 0.40667778413274824, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.081361032037703, + "apex_y": 1.2522033525119995, + "margin": 0.8813610320377032, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.457920111794784, + "apex_y": 1.2522033525119995, + "margin": 1.2579201117947836, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.457920111794784, + "apex_y": 1.2522033525119995, + "margin": 0.2579201117947836, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm10-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm10-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm10-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.607874870605464, + "apex_y": 1.2522033525119995, + "margin": 1.4078748706054638, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.8390549118964077, + "apex_y": 1.2522033525119995, + "margin": 0.6390549118964075, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.607874870605464, + "apex_y": 1.2522033525119995, + "margin": 0.4078748706054638, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.082667103931904, + "apex_y": 1.2522033525119995, + "margin": 0.8826671039319036, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.459300313631108, + "apex_y": 1.2522033525119995, + "margin": 1.2593003136311074, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.459300313631108, + "apex_y": 1.2522033525119995, + "margin": 0.2593003136311074, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.607874870605464, + "apex_y": 1.2522033525119995, + "margin": 1.4078748706054638, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.8390549118964077, + "apex_y": 1.2522033525119995, + "margin": 0.6390549118964075, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.607874870605464, + "apex_y": 1.2522033525119995, + "margin": 0.4078748706054638, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.082667103931904, + "apex_y": 1.2522033525119995, + "margin": 0.8826671039319036, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.459300313631108, + "apex_y": 1.2522033525119995, + "margin": 1.2593003136311074, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.459300313631108, + "apex_y": 1.2522033525119995, + "margin": 0.2593003136311074, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm11-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm11-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm11-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.608528479819567, + "apex_y": 1.2522033525119995, + "margin": 1.4085284798195667, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 2.839588612325821, + "apex_y": 1.2522033525119995, + "margin": 0.639588612325821, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.608528479819567, + "apex_y": 1.2522033525119995, + "margin": 0.40852847981956675, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.083380219186137, + "apex_y": 1.2522033525119995, + "margin": 0.8833802191861366, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4600539038337415, + "apex_y": 1.2522033525119995, + "margin": 1.2600539038337413, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4600539038337415, + "apex_y": 1.2522033525119995, + "margin": 0.2600539038337413, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.608528479819567, + "apex_y": 1.2522033525119995, + "margin": 1.4085284798195667, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 2.839588612325821, + "apex_y": 1.2522033525119995, + "margin": 0.639588612325821, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.608528479819567, + "apex_y": 1.2522033525119995, + "margin": 0.40852847981956675, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.083380219186137, + "apex_y": 1.2522033525119995, + "margin": 0.8833802191861366, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4600539038337415, + "apex_y": 1.2522033525119995, + "margin": 1.2600539038337413, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4600539038337415, + "apex_y": 1.2522033525119995, + "margin": 0.2600539038337413, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-walk-mm12-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-walk-mm12-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-walk-mm12-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "walk", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.0951266201794545, + "apex_y": 1.2522033525119995, + "margin": 0.8951266201794543, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.9214793175587332, + "apex_y": 1.2522033525119995, + "margin": 0.7214793175587331, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.424822798944206, + "apex_y": 1.2522033525119995, + "margin": 1.2248227989442055, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.424822798944206, + "apex_y": 1.2522033525119995, + "margin": 0.22482279894420554, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.820876058934151, + "apex_y": 1.2522033525119995, + "margin": 0.6208760589341509, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.0951266201794545, + "apex_y": 1.2522033525119995, + "margin": 0.8951266201794543, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.9214793175587332, + "apex_y": 1.2522033525119995, + "margin": 0.7214793175587331, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.424822798944206, + "apex_y": 1.2522033525119995, + "margin": 1.2248227989442055, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.424822798944206, + "apex_y": 1.2522033525119995, + "margin": 0.22482279894420554, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.820876058934151, + "apex_y": 1.2522033525119995, + "margin": 0.6208760589341509, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm0-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm0-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm0-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 0, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.5102512319825148, + "apex_y": 1.2522033525119995, + "margin": 1.3102512319825146, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4298717720554635, + "apex_y": 1.2522033525119995, + "margin": 1.2298717720554633, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.5102512319825148, + "apex_y": 1.2522033525119995, + "margin": 0.3102512319825146, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.4298717720554635, + "apex_y": 1.2522033525119995, + "margin": 0.22987177205546327, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.979500436003543, + "apex_y": 1.2522033525119995, + "margin": 0.7795004360035431, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.407036052604428, + "apex_y": 1.2522033525119995, + "margin": 1.207036052604428, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.407036052604428, + "apex_y": 1.2522033525119995, + "margin": 0.20703605260442792, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.5102512319825148, + "apex_y": 1.2522033525119995, + "margin": 1.3102512319825146, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4298717720554635, + "apex_y": 1.2522033525119995, + "margin": 1.2298717720554633, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.5102512319825148, + "apex_y": 1.2522033525119995, + "margin": 0.3102512319825146, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.4298717720554635, + "apex_y": 1.2522033525119995, + "margin": 0.22987177205546327, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.979500436003543, + "apex_y": 1.2522033525119995, + "margin": 0.7795004360035431, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.407036052604428, + "apex_y": 1.2522033525119995, + "margin": 1.207036052604428, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.407036052604428, + "apex_y": 1.2522033525119995, + "margin": 0.20703605260442792, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm1-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm1-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm1-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 1, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.7369092700269855, + "apex_y": 1.2522033525119995, + "margin": 1.5369092700269853, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.707454052210679, + "apex_y": 1.2522033525119995, + "margin": 1.5074540522106785, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.7369092700269855, + "apex_y": 1.2522033525119995, + "margin": 0.5369092700269853, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.707454052210679, + "apex_y": 1.2522033525119995, + "margin": 0.5074540522106785, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.282354425837941, + "apex_y": 1.2522033525119995, + "margin": 1.0823544258379405, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.282354425837941, + "apex_y": 1.2522033525119995, + "margin": 0.0823544258379405, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.727079409148399, + "apex_y": 1.2522033525119995, + "margin": 0.5270794091483992, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.7369092700269855, + "apex_y": 1.2522033525119995, + "margin": 1.5369092700269853, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.707454052210679, + "apex_y": 1.2522033525119995, + "margin": 1.5074540522106785, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.7369092700269855, + "apex_y": 1.2522033525119995, + "margin": 0.5369092700269853, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.707454052210679, + "apex_y": 1.2522033525119995, + "margin": 0.5074540522106785, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.282354425837941, + "apex_y": 1.2522033525119995, + "margin": 1.0823544258379405, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.282354425837941, + "apex_y": 1.2522033525119995, + "margin": 0.0823544258379405, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.727079409148399, + "apex_y": 1.2522033525119995, + "margin": 0.5270794091483992, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm2-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm2-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm2-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 2, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.8606645587992663, + "apex_y": 1.2522033525119995, + "margin": 0.6606645587992661, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.859013977175426, + "apex_y": 1.2522033525119995, + "margin": 0.6590139771754258, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.447712704287522, + "apex_y": 1.2522033525119995, + "margin": 1.2477127042875216, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.447712704287522, + "apex_y": 1.2522033525119995, + "margin": 0.24771270428752157, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.901823081821408, + "apex_y": 1.2522033525119995, + "margin": 0.7018230818214075, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.8606645587992663, + "apex_y": 1.2522033525119995, + "margin": 0.6606645587992661, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.859013977175426, + "apex_y": 1.2522033525119995, + "margin": 0.6590139771754258, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.447712704287522, + "apex_y": 1.2522033525119995, + "margin": 1.2477127042875216, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.447712704287522, + "apex_y": 1.2522033525119995, + "margin": 0.24771270428752157, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.901823081821408, + "apex_y": 1.2522033525119995, + "margin": 0.7018230818214075, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm3-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm3-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm3-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 3, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.928234946468933, + "apex_y": 1.2522033525119995, + "margin": 0.7282349464689326, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.941765696206179, + "apex_y": 1.2522033525119995, + "margin": 0.7417656962061789, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.537998324320994, + "apex_y": 1.2522033525119995, + "margin": 1.3379983243209939, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.537998324320994, + "apex_y": 1.2522033525119995, + "margin": 0.33799832432099386, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.997233127100872, + "apex_y": 1.2522033525119995, + "margin": 0.7972331271008715, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.928234946468933, + "apex_y": 1.2522033525119995, + "margin": 0.7282349464689326, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.941765696206179, + "apex_y": 1.2522033525119995, + "margin": 0.7417656962061789, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.537998324320994, + "apex_y": 1.2522033525119995, + "margin": 1.3379983243209939, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.537998324320994, + "apex_y": 1.2522033525119995, + "margin": 0.33799832432099386, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.997233127100872, + "apex_y": 1.2522033525119995, + "margin": 0.7972331271008715, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm4-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm4-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm4-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 4, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.9651283781365696, + "apex_y": 1.2522033525119995, + "margin": 0.7651283781365694, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.986948134796969, + "apex_y": 1.2522033525119995, + "margin": 0.7869481347969689, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.5872942728592685, + "apex_y": 1.2522033525119995, + "margin": 1.3872942728592683, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.5872942728592685, + "apex_y": 1.2522033525119995, + "margin": 0.3872942728592683, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.049327011823457, + "apex_y": 1.2522033525119995, + "margin": 0.8493270118234566, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.9651283781365696, + "apex_y": 1.2522033525119995, + "margin": 0.7651283781365694, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.986948134796969, + "apex_y": 1.2522033525119995, + "margin": 0.7869481347969689, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.5872942728592685, + "apex_y": 1.2522033525119995, + "margin": 1.3872942728592683, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.5872942728592685, + "apex_y": 1.2522033525119995, + "margin": 0.3872942728592683, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.049327011823457, + "apex_y": 1.2522033525119995, + "margin": 0.8493270118234566, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm5-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm5-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm5-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 5, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.9852721918270992, + "apex_y": 1.2522033525119995, + "margin": 0.7852721918270991, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.01161774626754, + "apex_y": 1.2522033525119995, + "margin": 0.8116177462675402, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.614209860761167, + "apex_y": 1.2522033525119995, + "margin": 1.4142098607611668, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.614209860761167, + "apex_y": 1.2522033525119995, + "margin": 0.41420986076116684, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.07777027288199, + "apex_y": 1.2522033525119995, + "margin": 0.8777702728819898, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.9852721918270992, + "apex_y": 1.2522033525119995, + "margin": 0.7852721918270991, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.01161774626754, + "apex_y": 1.2522033525119995, + "margin": 0.8116177462675402, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.614209860761167, + "apex_y": 1.2522033525119995, + "margin": 1.4142098607611668, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.614209860761167, + "apex_y": 1.2522033525119995, + "margin": 0.41420986076116684, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.07777027288199, + "apex_y": 1.2522033525119995, + "margin": 0.8777702728819898, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm6-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm6-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm6-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 6, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 3.9962707141021294, + "apex_y": 1.2522033525119995, + "margin": 0.7962707141021292, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.025087354130473, + "apex_y": 1.2522033525119995, + "margin": 0.8250873541304724, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.628905771755603, + "apex_y": 1.2522033525119995, + "margin": 1.4289057717556028, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.628905771755603, + "apex_y": 1.2522033525119995, + "margin": 0.42890577175560285, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.093300293419947, + "apex_y": 1.2522033525119995, + "margin": 0.893300293419947, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 3.9962707141021294, + "apex_y": 1.2522033525119995, + "margin": 0.7962707141021292, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.025087354130473, + "apex_y": 1.2522033525119995, + "margin": 0.8250873541304724, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.628905771755603, + "apex_y": 1.2522033525119995, + "margin": 1.4289057717556028, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.628905771755603, + "apex_y": 1.2522033525119995, + "margin": 0.42890577175560285, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.093300293419947, + "apex_y": 1.2522033525119995, + "margin": 0.893300293419947, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm7-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm7-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm7-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 7, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.002275907264296, + "apex_y": 1.2522033525119995, + "margin": 0.8022759072642955, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.032441760023634, + "apex_y": 1.2522033525119995, + "margin": 0.8324417600236336, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.636929739158566, + "apex_y": 1.2522033525119995, + "margin": 1.4369297391585656, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.636929739158566, + "apex_y": 1.2522033525119995, + "margin": 0.4369297391585656, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.101779684633674, + "apex_y": 1.2522033525119995, + "margin": 0.9017796846336736, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.002275907264296, + "apex_y": 1.2522033525119995, + "margin": 0.8022759072642955, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.032441760023634, + "apex_y": 1.2522033525119995, + "margin": 0.8324417600236336, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.636929739158566, + "apex_y": 1.2522033525119995, + "margin": 1.4369297391585656, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.636929739158566, + "apex_y": 1.2522033525119995, + "margin": 0.4369297391585656, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.101779684633674, + "apex_y": 1.2522033525119995, + "margin": 0.9017796846336736, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm8-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm8-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm8-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 8, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.005554742730838, + "apex_y": 1.2522033525119995, + "margin": 0.8055547427308376, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.0364572656413005, + "apex_y": 1.2522033525119995, + "margin": 0.8364572656413003, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.641310825360584, + "apex_y": 1.2522033525119995, + "margin": 1.441310825360584, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.641310825360584, + "apex_y": 1.2522033525119995, + "margin": 0.44131082536058397, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.106409432236369, + "apex_y": 1.2522033525119995, + "margin": 0.9064094322363685, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.005554742730838, + "apex_y": 1.2522033525119995, + "margin": 0.8055547427308376, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.0364572656413005, + "apex_y": 1.2522033525119995, + "margin": 0.8364572656413003, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.641310825360584, + "apex_y": 1.2522033525119995, + "margin": 1.441310825360584, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.641310825360584, + "apex_y": 1.2522033525119995, + "margin": 0.44131082536058397, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.106409432236369, + "apex_y": 1.2522033525119995, + "margin": 0.9064094322363685, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm9-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm9-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm9-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 9, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.00734498689557, + "apex_y": 1.2522033525119995, + "margin": 0.8073449868955702, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.038649731708546, + "apex_y": 1.2522033525119995, + "margin": 0.8386497317085455, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.643702898426886, + "apex_y": 1.2522033525119995, + "margin": 1.4437028984268858, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.643702898426886, + "apex_y": 1.2522033525119995, + "margin": 0.4437028984268858, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.10893727442744, + "apex_y": 1.2522033525119995, + "margin": 0.9089372744274398, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.00734498689557, + "apex_y": 1.2522033525119995, + "margin": 0.8073449868955702, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.038649731708546, + "apex_y": 1.2522033525119995, + "margin": 0.8386497317085455, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.643702898426886, + "apex_y": 1.2522033525119995, + "margin": 1.4437028984268858, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.643702898426886, + "apex_y": 1.2522033525119995, + "margin": 0.4437028984268858, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.10893727442744, + "apex_y": 1.2522033525119995, + "margin": 0.9089372744274398, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm10-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm10-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm10-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 10, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.008322460209515, + "apex_y": 1.2522033525119995, + "margin": 0.8083224602095145, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.039846818181262, + "apex_y": 1.2522033525119995, + "margin": 0.839846818181262, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.645008970321087, + "apex_y": 1.2522033525119995, + "margin": 1.445008970321087, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.645008970321087, + "apex_y": 1.2522033525119995, + "margin": 0.4450089703210871, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.1103174762637655, + "apex_y": 1.2522033525119995, + "margin": 0.9103174762637654, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.008322460209515, + "apex_y": 1.2522033525119995, + "margin": 0.8083224602095145, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.039846818181262, + "apex_y": 1.2522033525119995, + "margin": 0.839846818181262, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.645008970321087, + "apex_y": 1.2522033525119995, + "margin": 1.445008970321087, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.645008970321087, + "apex_y": 1.2522033525119995, + "margin": 0.4450089703210871, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.1103174762637655, + "apex_y": 1.2522033525119995, + "margin": 0.9103174762637654, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm11-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm11-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm11-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 11, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap0-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap0-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap0-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap0-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap1-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap1-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap1-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap1-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap2-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap2-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap2-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap2-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap3-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap3-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 4.008856160638927, + "apex_y": 1.2522033525119995, + "margin": 0.8088561606389266, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap3-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap3-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap4-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.040500427395363, + "apex_y": 1.2522033525119995, + "margin": 0.8405004273953631, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap4-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap4-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.645722085575318, + "apex_y": 1.2522033525119995, + "margin": 1.4457220855753175, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap4-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap5-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap5-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap5-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 5.645722085575318, + "apex_y": 1.2522033525119995, + "margin": 0.44572208557531745, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap5-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": true, + "landing_x": 6.111071066466395, + "apex_y": 1.2522033525119995, + "margin": 0.9110710664663948, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap6-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap6-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap6-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap6-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap7-dy0p0-wo0", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap7-dy1p0-wo0", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap7-dym1p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap7-dym2p0-wo0", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 0, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap0-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap0-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap0-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap0-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 0, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap1-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap1-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap1-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap1-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 1, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap2-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap2-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap2-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap2-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 2, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap3-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap3-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 4.008856160638927, + "apex_y": 1.2522033525119995, + "margin": 0.8088561606389266, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap3-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap3-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 3, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap4-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.040500427395363, + "apex_y": 1.2522033525119995, + "margin": 0.8405004273953631, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap4-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap4-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.645722085575318, + "apex_y": 1.2522033525119995, + "margin": 1.4457220855753175, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap4-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 4, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap5-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap5-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap5-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 5.645722085575318, + "apex_y": 1.2522033525119995, + "margin": 0.44572208557531745, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap5-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 5, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": true, + "landing_x": 6.111071066466395, + "apex_y": 1.2522033525119995, + "margin": 0.9110710664663948, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap6-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap6-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap6-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap6-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 6, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-flat-sprint-mm12-gap7-dy0p0-wo1", + "family": "sidewall", + "subfamily": "flat", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 0.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-ascend-sprint-mm12-gap7-dy1p0-wo1", + "family": "sidewall", + "subfamily": "ascend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": 1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap7-dym1p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -1.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + }, + { + "case_id": "sidewall-descend-sprint-mm12-gap7-dym2p0-wo1", + "family": "sidewall", + "subfamily": "descend", + "movement_mode": "sprint", + "momentum_ticks": 12, + "gap_blocks": 7, + "delta_y": -2.0, + "ceiling_height": null, + "wall_width": null, + "wall_offset": 1, + "expected_reachable": false, + "landing_x": null, + "apex_y": 1.2522033525119995, + "margin": null, + "notes": "" + } +] diff --git a/tools/pathing_data/theory-matrix.md b/tools/pathing_data/theory-matrix.md new file mode 100644 index 0000000000..878836229c --- /dev/null +++ b/tools/pathing_data/theory-matrix.md @@ -0,0 +1,2869 @@ +# Theory Matrix + +## Canonical live coverage + +This file is generated from `tools/pathing_theory/simulator.py` and is the first-wave authority +for theory-aligned linear, neo, headhitter, and sidewall live suites. + +| family | subfamily | movement_mode | case_id | expected_reachable | margin | +| --- | --- | --- | --- | --- | --- | +| linear | flat | walk | linear-flat-walk-mm0-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm0-gap1-dy0p0 | True | 1.317113405646343 | +| linear | ascend | walk | linear-ascend-walk-mm0-gap1-dy1p0 | True | 0.7447699239946501 | +| linear | descend | walk | linear-descend-walk-mm0-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm0-gap2-dy0p0 | True | 0.3171134056463427 | +| linear | ascend | walk | linear-ascend-walk-mm0-gap2-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap2-dym1p0 | True | 0.6958406866118745 | +| linear | descend | walk | linear-descend-walk-mm0-gap2-dym2p0 | True | 1.007960450907294 | +| linear | flat | walk | linear-flat-walk-mm0-gap3-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap3-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap3-dym2p0 | True | 0.007960450907293914 | +| linear | flat | walk | linear-flat-walk-mm0-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap4-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm0-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm0-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm0-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm0-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm0-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap1-dy1p0 | True | 1.1662985020682168 | +| linear | descend | walk | linear-descend-walk-mm1-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap2-dy0p0 | True | 0.8333486332242361 | +| linear | ascend | walk | linear-ascend-walk-mm1-gap2-dy1p0 | True | 0.1662985020682166 | +| linear | descend | walk | linear-descend-walk-mm1-gap2-dym1p0 | True | 1.259075120299828 | +| linear | descend | walk | linear-descend-walk-mm1-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap3-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap3-dym1p0 | True | 0.25907512029982804 | +| linear | descend | walk | linear-descend-walk-mm1-gap3-dym2p0 | True | 0.6031629073028735 | +| linear | flat | walk | linear-flat-walk-mm1-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap4-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm1-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm1-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm1-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap1-dy1p0 | True | 1.396453105696384 | +| linear | descend | walk | linear-descend-walk-mm2-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap2-dy0p0 | True | 1.1152130674817657 | +| linear | ascend | walk | linear-ascend-walk-mm2-gap2-dy1p0 | True | 0.3964531056963838 | +| linear | descend | walk | linear-descend-walk-mm2-gap2-dym1p0 | True | 1.5666011210934503 | +| linear | descend | walk | linear-descend-walk-mm2-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap3-dy0p0 | True | 0.11521306748176574 | +| linear | ascend | walk | linear-ascend-walk-mm2-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap3-dym1p0 | True | 0.5666011210934503 | +| linear | descend | walk | linear-descend-walk-mm2-gap3-dym2p0 | True | 0.9281434484948594 | +| linear | flat | walk | linear-flat-walk-mm2-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap4-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm2-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm2-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm2-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap1-dy1p0 | True | 1.5221175192773624 | +| linear | descend | walk | linear-descend-walk-mm3-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap2-dy0p0 | True | 1.269111048586376 | +| linear | ascend | walk | linear-ascend-walk-mm3-gap2-dy1p0 | True | 0.5221175192773622 | +| linear | descend | walk | linear-descend-walk-mm3-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap3-dy0p0 | True | 0.2691110485863759 | +| linear | ascend | walk | linear-ascend-walk-mm3-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap3-dym1p0 | True | 0.7345103175267673 | +| linear | descend | walk | linear-descend-walk-mm3-gap3-dym2p0 | True | 1.1055828239856833 | +| linear | flat | walk | linear-flat-walk-mm3-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap4-dym2p0 | True | 0.1055828239856833 | +| linear | flat | walk | linear-flat-walk-mm3-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm3-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm3-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm3-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap1-dy1p0 | True | 1.5907302890925779 | +| linear | descend | walk | linear-descend-walk-mm4-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap2-dy0p0 | True | 1.353139346269494 | +| linear | ascend | walk | linear-ascend-walk-mm4-gap2-dy1p0 | True | 0.5907302890925776 | +| linear | descend | walk | linear-descend-walk-mm4-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap3-dy0p0 | True | 0.35313934626949406 | +| linear | ascend | walk | linear-ascend-walk-mm4-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap3-dym1p0 | True | 0.8261887387793596 | +| linear | descend | walk | linear-descend-walk-mm4-gap3-dym2p0 | True | 1.202464723003673 | +| linear | flat | walk | linear-flat-walk-mm4-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap4-dym2p0 | True | 0.2024647230036729 | +| linear | flat | walk | linear-flat-walk-mm4-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm4-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm4-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm4-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap2-dy0p0 | True | 1.399018796804477 | +| linear | ascend | walk | linear-ascend-walk-mm5-gap2-dy1p0 | True | 0.628192861411685 | +| linear | descend | walk | linear-descend-walk-mm5-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap3-dy0p0 | True | 0.39901879680447694 | +| linear | ascend | walk | linear-ascend-walk-mm5-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap3-dym1p0 | True | 0.8762451567832752 | +| linear | descend | walk | linear-descend-walk-mm5-gap3-dym2p0 | True | 1.2553622398674973 | +| linear | flat | walk | linear-flat-walk-mm5-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap4-dym2p0 | True | 0.2553622398674973 | +| linear | flat | walk | linear-flat-walk-mm5-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm5-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm5-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm5-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap2-dy0p0 | True | 1.4240689767965766 | +| linear | ascend | walk | linear-ascend-walk-mm6-gap2-dy1p0 | True | 0.6486474258979178 | +| linear | descend | walk | linear-descend-walk-mm6-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap3-dy0p0 | True | 0.4240689767965766 | +| linear | ascend | walk | linear-ascend-walk-mm6-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap3-dym1p0 | True | 0.903575961013412 | +| linear | descend | walk | linear-descend-walk-mm6-gap3-dym2p0 | True | 1.284244284075144 | +| linear | flat | walk | linear-flat-walk-mm6-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap4-dym2p0 | True | 0.284244284075144 | +| linear | flat | walk | linear-flat-walk-mm6-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm6-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm6-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm6-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap2-dy0p0 | True | 1.4377463750722632 | +| linear | ascend | walk | linear-ascend-walk-mm7-gap2-dy1p0 | True | 0.6598156181074004 | +| linear | descend | walk | linear-descend-walk-mm7-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap3-dy0p0 | True | 0.4377463750722632 | +| linear | ascend | walk | linear-ascend-walk-mm7-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap3-dym1p0 | True | 0.9184985801230665 | +| linear | descend | walk | linear-descend-walk-mm7-gap3-dym2p0 | True | 1.3000138802125187 | +| linear | flat | walk | linear-flat-walk-mm7-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap4-dym2p0 | True | 0.30001388021251874 | +| linear | flat | walk | linear-flat-walk-mm7-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm7-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm7-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm7-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap2-dy0p0 | True | 1.4452142345307886 | +| linear | ascend | walk | linear-ascend-walk-mm8-gap2-dy1p0 | True | 0.665913451053779 | +| linear | descend | walk | linear-descend-walk-mm8-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap3-dy0p0 | True | 0.44521423453078857 | +| linear | ascend | walk | linear-ascend-walk-mm8-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap3-dym1p0 | True | 0.926646330156939 | +| linear | descend | walk | linear-descend-walk-mm8-gap3-dym2p0 | True | 1.3086240797035265 | +| linear | flat | walk | linear-flat-walk-mm8-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap4-dym2p0 | True | 0.30862407970352645 | +| linear | flat | walk | linear-flat-walk-mm8-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm8-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm8-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm8-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap2-dy0p0 | True | 1.4492916857951426 | +| linear | ascend | walk | linear-ascend-walk-mm9-gap2-dy1p0 | True | 0.6692428678425002 | +| linear | descend | walk | linear-descend-walk-mm9-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap3-dy0p0 | True | 0.44929168579514256 | +| linear | ascend | walk | linear-ascend-walk-mm9-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap3-dym1p0 | True | 0.9310950016754322 | +| linear | descend | walk | linear-descend-walk-mm9-gap3-dym2p0 | True | 1.3133252486256142 | +| linear | flat | walk | linear-flat-walk-mm9-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap4-dym2p0 | True | 0.31332524862561417 | +| linear | flat | walk | linear-flat-walk-mm9-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm9-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm9-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm9-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap2-dy0p0 | True | 1.4515179741854807 | +| linear | ascend | walk | linear-ascend-walk-mm10-gap2-dy1p0 | True | 0.6710607294091431 | +| linear | descend | walk | linear-descend-walk-mm10-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap3-dy0p0 | True | 0.4515179741854807 | +| linear | ascend | walk | linear-ascend-walk-mm10-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap3-dym1p0 | True | 0.9335239763245298 | +| linear | descend | walk | linear-descend-walk-mm10-gap3-dym2p0 | True | 1.315892086857076 | +| linear | flat | walk | linear-flat-walk-mm10-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap4-dym2p0 | True | 0.31589208685707604 | +| linear | flat | walk | linear-flat-walk-mm10-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm10-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm10-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm10-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap2-dy0p0 | True | 1.452733527646605 | +| linear | ascend | walk | linear-ascend-walk-mm11-gap2-dy1p0 | True | 0.6720532818245295 | +| linear | descend | walk | linear-descend-walk-mm11-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap3-dy0p0 | True | 0.4527335276466049 | +| linear | ascend | walk | linear-ascend-walk-mm11-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap3-dym1p0 | True | 0.9348501964829383 | +| linear | descend | walk | linear-descend-walk-mm11-gap3-dym2p0 | True | 1.3172935805314552 | +| linear | flat | walk | linear-flat-walk-mm11-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap4-dym2p0 | True | 0.3172935805314552 | +| linear | flat | walk | linear-flat-walk-mm11-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm11-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm11-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm11-gap7-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap0-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap0-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap0-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap0-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap1-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap1-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap1-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap1-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap2-dy0p0 | True | 1.4533972198363783 | +| linear | ascend | walk | linear-ascend-walk-mm12-gap2-dy1p0 | True | 0.6725952154433306 | +| linear | descend | walk | linear-descend-walk-mm12-gap2-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap2-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap3-dy0p0 | True | 0.45339721983637826 | +| linear | ascend | walk | linear-ascend-walk-mm12-gap3-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap3-dym1p0 | True | 0.9355743126894271 | +| linear | descend | walk | linear-descend-walk-mm12-gap3-dym2p0 | True | 1.318058796077664 | +| linear | flat | walk | linear-flat-walk-mm12-gap4-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap4-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap4-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap4-dym2p0 | True | 0.31805879607766396 | +| linear | flat | walk | linear-flat-walk-mm12-gap5-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap5-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap5-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap5-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap6-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap6-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap6-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap6-dym2p0 | False | None | +| linear | flat | walk | linear-flat-walk-mm12-gap7-dy0p0 | False | None | +| linear | ascend | walk | linear-ascend-walk-mm12-gap7-dy1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap7-dym1p0 | False | None | +| linear | descend | walk | linear-descend-walk-mm12-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap2-dy1p0 | True | 0.9320753089661786 | +| linear | descend | sprint | linear-descend-sprint-mm0-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap3-dy0p0 | True | 0.7711758286886656 | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap3-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap3-dym1p0 | True | 1.2822841946066923 | +| linear | descend | sprint | linear-descend-sprint-mm0-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap4-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap4-dym1p0 | True | 0.2822841946066923 | +| linear | descend | sprint | linear-descend-sprint-mm0-gap4-dym2p0 | True | 0.684447214524587 | +| linear | flat | sprint | linear-flat-sprint-mm0-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap5-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap5-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm0-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm0-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm0-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap2-dy1p0 | True | 1.3536038870397449 | +| linear | descend | sprint | linear-descend-sprint-mm1-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap3-dy0p0 | True | 1.2874110562665573 | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap3-dy1p0 | True | 0.3536038870397449 | +| linear | descend | sprint | linear-descend-sprint-mm1-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap4-dy0p0 | True | 0.2874110562665573 | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap4-dym1p0 | True | 0.8455186282946441 | +| linear | descend | sprint | linear-descend-sprint-mm1-gap4-dym2p0 | True | 1.2796496709201648 | +| linear | flat | sprint | linear-flat-sprint-mm1-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap5-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap5-dym2p0 | True | 0.2796496709201648 | +| linear | flat | sprint | linear-flat-sprint-mm1-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm1-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm1-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm1-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap2-dy1p0 | True | 1.583758490667913 | +| linear | descend | sprint | linear-descend-sprint-mm2-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap3-dy0p0 | True | 1.569275490524089 | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap3-dy1p0 | True | 0.583758490667913 | +| linear | descend | sprint | linear-descend-sprint-mm2-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap4-dy0p0 | True | 0.5692754905240891 | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap4-dym1p0 | True | 1.153044629088269 | +| linear | descend | sprint | linear-descend-sprint-mm2-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap5-dym1p0 | True | 0.153044629088269 | +| linear | descend | sprint | linear-descend-sprint-mm2-gap5-dym2p0 | True | 0.6046302121121521 | +| linear | flat | sprint | linear-flat-sprint-mm2-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm2-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm2-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm2-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap3-dy1p0 | True | 0.7094229042488918 | +| linear | descend | sprint | linear-descend-sprint-mm3-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap4-dy0p0 | True | 0.7231734716286997 | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap4-dym1p0 | True | 1.3209538255215856 | +| linear | descend | sprint | linear-descend-sprint-mm3-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap5-dym1p0 | True | 0.3209538255215856 | +| linear | descend | sprint | linear-descend-sprint-mm3-gap5-dym2p0 | True | 0.7820695876029768 | +| linear | flat | sprint | linear-flat-sprint-mm3-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm3-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm3-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm3-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap3-dy1p0 | True | 0.7780356740641068 | +| linear | descend | sprint | linear-descend-sprint-mm4-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap4-dy0p0 | True | 0.807201769311817 | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap4-dym1p0 | True | 1.4126322467741765 | +| linear | descend | sprint | linear-descend-sprint-mm4-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap5-dym1p0 | True | 0.41263224677417654 | +| linear | descend | sprint | linear-descend-sprint-mm4-gap5-dym2p0 | True | 0.8789514866209656 | +| linear | flat | sprint | linear-flat-sprint-mm4-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm4-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm4-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm4-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap3-dy1p0 | True | 0.8154982463832141 | +| linear | descend | sprint | linear-descend-sprint-mm5-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap4-dy0p0 | True | 0.8530812198467999 | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap4-dym1p0 | True | 1.4626886647780921 | +| linear | descend | sprint | linear-descend-sprint-mm5-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap5-dym1p0 | True | 0.4626886647780921 | +| linear | descend | sprint | linear-descend-sprint-mm5-gap5-dym2p0 | True | 0.931849003484789 | +| linear | flat | sprint | linear-flat-sprint-mm5-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm5-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm5-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm5-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap3-dy1p0 | True | 0.835952810869447 | +| linear | descend | sprint | linear-descend-sprint-mm6-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap4-dy0p0 | True | 0.8781313998389004 | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap4-dym1p0 | True | 1.4900194690082298 | +| linear | descend | sprint | linear-descend-sprint-mm6-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap5-dym1p0 | True | 0.4900194690082298 | +| linear | descend | sprint | linear-descend-sprint-mm6-gap5-dym2p0 | True | 0.9607310476924358 | +| linear | flat | sprint | linear-flat-sprint-mm6-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm6-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm6-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm6-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap3-dy1p0 | True | 0.8471210030789296 | +| linear | descend | sprint | linear-descend-sprint-mm7-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap4-dy0p0 | True | 0.8918087981145861 | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap4-dym1p0 | True | 1.5049420881178852 | +| linear | descend | sprint | linear-descend-sprint-mm7-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap5-dym1p0 | True | 0.5049420881178852 | +| linear | descend | sprint | linear-descend-sprint-mm7-gap5-dym2p0 | True | 0.9765006438298114 | +| linear | flat | sprint | linear-flat-sprint-mm7-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm7-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm7-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm7-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap3-dy1p0 | True | 0.8532188360253068 | +| linear | descend | sprint | linear-descend-sprint-mm8-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap4-dy0p0 | True | 0.8992766575731119 | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap4-dym1p0 | True | 1.5130898381517568 | +| linear | descend | sprint | linear-descend-sprint-mm8-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap5-dym1p0 | True | 0.5130898381517568 | +| linear | descend | sprint | linear-descend-sprint-mm8-gap5-dym2p0 | True | 0.9851108433208191 | +| linear | flat | sprint | linear-flat-sprint-mm8-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm8-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm8-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm8-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap3-dy1p0 | True | 0.8565482528140294 | +| linear | descend | sprint | linear-descend-sprint-mm9-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap4-dy0p0 | True | 0.9033541088374646 | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap4-dym1p0 | True | 1.5175385096702492 | +| linear | descend | sprint | linear-descend-sprint-mm9-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap5-dym1p0 | True | 0.5175385096702492 | +| linear | descend | sprint | linear-descend-sprint-mm9-gap5-dym2p0 | True | 0.9898120122429068 | +| linear | flat | sprint | linear-flat-sprint-mm9-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm9-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm9-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm9-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap3-dy1p0 | True | 0.8583661143806713 | +| linear | descend | sprint | linear-descend-sprint-mm10-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap4-dy0p0 | True | 0.9055803972278049 | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap4-dym1p0 | True | 1.5199674843193485 | +| linear | descend | sprint | linear-descend-sprint-mm10-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap5-dym1p0 | True | 0.5199674843193485 | +| linear | descend | sprint | linear-descend-sprint-mm10-gap5-dym2p0 | True | 0.9923788504743687 | +| linear | flat | sprint | linear-flat-sprint-mm10-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm10-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm10-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm10-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap3-dy1p0 | True | 0.8593586667960587 | +| linear | descend | sprint | linear-descend-sprint-mm11-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap4-dy0p0 | True | 0.9067959506889283 | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap4-dym1p0 | True | 1.5212937044777561 | +| linear | descend | sprint | linear-descend-sprint-mm11-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap5-dym1p0 | True | 0.5212937044777561 | +| linear | descend | sprint | linear-descend-sprint-mm11-gap5-dym2p0 | True | 0.9937803441487469 | +| linear | flat | sprint | linear-flat-sprint-mm11-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm11-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm11-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm11-gap7-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap0-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap0-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap0-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap0-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap1-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap1-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap1-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap1-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap2-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap2-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap2-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap2-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap3-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap3-dy1p0 | True | 0.8599006004148597 | +| linear | descend | sprint | linear-descend-sprint-mm12-gap3-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap3-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap4-dy0p0 | True | 0.9074596428787016 | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap4-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap4-dym1p0 | True | 1.522017820684245 | +| linear | descend | sprint | linear-descend-sprint-mm12-gap4-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap5-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap5-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap5-dym1p0 | True | 0.522017820684245 | +| linear | descend | sprint | linear-descend-sprint-mm12-gap5-dym2p0 | True | 0.9945455596949557 | +| linear | flat | sprint | linear-flat-sprint-mm12-gap6-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap6-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap6-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap6-dym2p0 | False | None | +| linear | flat | sprint | linear-flat-sprint-mm12-gap7-dy0p0 | False | None | +| linear | ascend | sprint | linear-ascend-sprint-mm12-gap7-dy1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap7-dym1p0 | False | None | +| linear | descend | sprint | linear-descend-sprint-mm12-gap7-dym2p0 | False | None | +| neo | neo | walk | neo-neo-walk-mm0-wall1 | True | 0.9171134056463428 | +| neo | neo | walk | neo-neo-walk-mm0-wall2 | False | -0.0828865943536572 | +| neo | neo | walk | neo-neo-walk-mm0-wall3 | False | -1.0828865943536572 | +| neo | neo | walk | neo-neo-walk-mm0-wall4 | False | -2.0828865943536568 | +| neo | neo | walk | neo-neo-walk-mm1-wall1 | True | 1.4333486332242362 | +| neo | neo | walk | neo-neo-walk-mm1-wall2 | True | 0.4333486332242362 | +| neo | neo | walk | neo-neo-walk-mm1-wall3 | False | -0.5666513667757638 | +| neo | neo | walk | neo-neo-walk-mm1-wall4 | False | -1.5666513667757633 | +| neo | neo | walk | neo-neo-walk-mm2-wall1 | True | 1.7152130674817658 | +| neo | neo | walk | neo-neo-walk-mm2-wall2 | True | 0.7152130674817658 | +| neo | neo | walk | neo-neo-walk-mm2-wall3 | False | -0.28478693251823417 | +| neo | neo | walk | neo-neo-walk-mm2-wall4 | False | -1.2847869325182337 | +| neo | neo | walk | neo-neo-walk-mm3-wall1 | True | 1.869111048586376 | +| neo | neo | walk | neo-neo-walk-mm3-wall2 | True | 0.869111048586376 | +| neo | neo | walk | neo-neo-walk-mm3-wall3 | False | -0.130888951413624 | +| neo | neo | walk | neo-neo-walk-mm3-wall4 | False | -1.1308889514136236 | +| neo | neo | walk | neo-neo-walk-mm4-wall1 | True | 1.9531393462694941 | +| neo | neo | walk | neo-neo-walk-mm4-wall2 | True | 0.9531393462694941 | +| neo | neo | walk | neo-neo-walk-mm4-wall3 | False | -0.04686065373050585 | +| neo | neo | walk | neo-neo-walk-mm4-wall4 | False | -1.0468606537305054 | +| neo | neo | walk | neo-neo-walk-mm5-wall1 | True | 1.999018796804477 | +| neo | neo | walk | neo-neo-walk-mm5-wall2 | True | 0.999018796804477 | +| neo | neo | walk | neo-neo-walk-mm5-wall3 | False | -0.0009812031955229727 | +| neo | neo | walk | neo-neo-walk-mm5-wall4 | False | -1.0009812031955225 | +| neo | neo | walk | neo-neo-walk-mm6-wall1 | True | 2.0240689767965767 | +| neo | neo | walk | neo-neo-walk-mm6-wall2 | True | 1.0240689767965767 | +| neo | neo | walk | neo-neo-walk-mm6-wall3 | True | 0.024068976796576713 | +| neo | neo | walk | neo-neo-walk-mm6-wall4 | False | -0.9759310232034228 | +| neo | neo | walk | neo-neo-walk-mm7-wall1 | True | 2.0377463750722633 | +| neo | neo | walk | neo-neo-walk-mm7-wall2 | True | 1.0377463750722633 | +| neo | neo | walk | neo-neo-walk-mm7-wall3 | True | 0.037746375072263305 | +| neo | neo | walk | neo-neo-walk-mm7-wall4 | False | -0.9622536249277363 | +| neo | neo | walk | neo-neo-walk-mm8-wall1 | True | 2.0452142345307887 | +| neo | neo | walk | neo-neo-walk-mm8-wall2 | True | 1.0452142345307887 | +| neo | neo | walk | neo-neo-walk-mm8-wall3 | True | 0.045214234530788655 | +| neo | neo | walk | neo-neo-walk-mm8-wall4 | False | -0.9547857654692109 | +| neo | neo | walk | neo-neo-walk-mm9-wall1 | True | 2.0492916857951426 | +| neo | neo | walk | neo-neo-walk-mm9-wall2 | True | 1.0492916857951426 | +| neo | neo | walk | neo-neo-walk-mm9-wall3 | True | 0.04929168579514265 | +| neo | neo | walk | neo-neo-walk-mm9-wall4 | False | -0.9507083142048569 | +| neo | neo | walk | neo-neo-walk-mm10-wall1 | True | 2.0515179741854808 | +| neo | neo | walk | neo-neo-walk-mm10-wall2 | True | 1.0515179741854808 | +| neo | neo | walk | neo-neo-walk-mm10-wall3 | True | 0.051517974185480764 | +| neo | neo | walk | neo-neo-walk-mm10-wall4 | False | -0.9484820258145188 | +| neo | neo | walk | neo-neo-walk-mm11-wall1 | True | 2.052733527646605 | +| neo | neo | walk | neo-neo-walk-mm11-wall2 | True | 1.052733527646605 | +| neo | neo | walk | neo-neo-walk-mm11-wall3 | True | 0.052733527646604994 | +| neo | neo | walk | neo-neo-walk-mm11-wall4 | False | -0.9472664723533946 | +| neo | neo | walk | neo-neo-walk-mm12-wall1 | True | 2.0533972198363784 | +| neo | neo | walk | neo-neo-walk-mm12-wall2 | True | 1.0533972198363784 | +| neo | neo | walk | neo-neo-walk-mm12-wall3 | True | 0.05339721983637835 | +| neo | neo | walk | neo-neo-walk-mm12-wall4 | False | -0.9466027801636212 | +| neo | neo | sprint | neo-neo-sprint-mm0-wall1 | True | 2.3711758286886657 | +| neo | neo | sprint | neo-neo-sprint-mm0-wall2 | True | 1.3711758286886657 | +| neo | neo | sprint | neo-neo-sprint-mm0-wall3 | True | 0.3711758286886657 | +| neo | neo | sprint | neo-neo-sprint-mm0-wall4 | False | -0.6288241713113338 | +| neo | neo | sprint | neo-neo-sprint-mm1-wall1 | True | 2.8874110562665574 | +| neo | neo | sprint | neo-neo-sprint-mm1-wall2 | True | 1.8874110562665574 | +| neo | neo | sprint | neo-neo-sprint-mm1-wall3 | True | 0.8874110562665574 | +| neo | neo | sprint | neo-neo-sprint-mm1-wall4 | False | -0.11258894373344219 | +| neo | neo | sprint | neo-neo-sprint-mm2-wall1 | True | 3.169275490524089 | +| neo | neo | sprint | neo-neo-sprint-mm2-wall2 | True | 2.169275490524089 | +| neo | neo | sprint | neo-neo-sprint-mm2-wall3 | True | 1.1692754905240892 | +| neo | neo | sprint | neo-neo-sprint-mm2-wall4 | True | 0.16927549052408963 | +| neo | neo | sprint | neo-neo-sprint-mm3-wall1 | True | 3.3231734716287 | +| neo | neo | sprint | neo-neo-sprint-mm3-wall2 | True | 2.3231734716287 | +| neo | neo | sprint | neo-neo-sprint-mm3-wall3 | True | 1.3231734716286998 | +| neo | neo | sprint | neo-neo-sprint-mm3-wall4 | True | 0.32317347162870025 | +| neo | neo | sprint | neo-neo-sprint-mm4-wall1 | True | 3.407201769311817 | +| neo | neo | sprint | neo-neo-sprint-mm4-wall2 | True | 2.407201769311817 | +| neo | neo | sprint | neo-neo-sprint-mm4-wall3 | True | 1.407201769311817 | +| neo | neo | sprint | neo-neo-sprint-mm4-wall4 | True | 0.4072017693118175 | +| neo | neo | sprint | neo-neo-sprint-mm5-wall1 | True | 3.4530812198468 | +| neo | neo | sprint | neo-neo-sprint-mm5-wall2 | True | 2.4530812198468 | +| neo | neo | sprint | neo-neo-sprint-mm5-wall3 | True | 1.4530812198468 | +| neo | neo | sprint | neo-neo-sprint-mm5-wall4 | True | 0.4530812198468004 | +| neo | neo | sprint | neo-neo-sprint-mm6-wall1 | True | 3.4781313998389005 | +| neo | neo | sprint | neo-neo-sprint-mm6-wall2 | True | 2.4781313998389005 | +| neo | neo | sprint | neo-neo-sprint-mm6-wall3 | True | 1.4781313998389005 | +| neo | neo | sprint | neo-neo-sprint-mm6-wall4 | True | 0.47813139983890096 | +| neo | neo | sprint | neo-neo-sprint-mm7-wall1 | True | 3.491808798114586 | +| neo | neo | sprint | neo-neo-sprint-mm7-wall2 | True | 2.491808798114586 | +| neo | neo | sprint | neo-neo-sprint-mm7-wall3 | True | 1.4918087981145862 | +| neo | neo | sprint | neo-neo-sprint-mm7-wall4 | True | 0.49180879811458666 | +| neo | neo | sprint | neo-neo-sprint-mm8-wall1 | True | 3.499276657573112 | +| neo | neo | sprint | neo-neo-sprint-mm8-wall2 | True | 2.499276657573112 | +| neo | neo | sprint | neo-neo-sprint-mm8-wall3 | True | 1.499276657573112 | +| neo | neo | sprint | neo-neo-sprint-mm8-wall4 | True | 0.49927665757311246 | +| neo | neo | sprint | neo-neo-sprint-mm9-wall1 | True | 3.5033541088374647 | +| neo | neo | sprint | neo-neo-sprint-mm9-wall2 | True | 2.5033541088374647 | +| neo | neo | sprint | neo-neo-sprint-mm9-wall3 | True | 1.5033541088374647 | +| neo | neo | sprint | neo-neo-sprint-mm9-wall4 | True | 0.5033541088374651 | +| neo | neo | sprint | neo-neo-sprint-mm10-wall1 | True | 3.505580397227805 | +| neo | neo | sprint | neo-neo-sprint-mm10-wall2 | True | 2.505580397227805 | +| neo | neo | sprint | neo-neo-sprint-mm10-wall3 | True | 1.505580397227805 | +| neo | neo | sprint | neo-neo-sprint-mm10-wall4 | True | 0.5055803972278055 | +| neo | neo | sprint | neo-neo-sprint-mm11-wall1 | True | 3.5067959506889284 | +| neo | neo | sprint | neo-neo-sprint-mm11-wall2 | True | 2.5067959506889284 | +| neo | neo | sprint | neo-neo-sprint-mm11-wall3 | True | 1.5067959506889284 | +| neo | neo | sprint | neo-neo-sprint-mm11-wall4 | True | 0.5067959506889288 | +| neo | neo | sprint | neo-neo-sprint-mm12-wall1 | True | 3.5074596428787017 | +| neo | neo | sprint | neo-neo-sprint-mm12-wall2 | True | 2.5074596428787017 | +| neo | neo | sprint | neo-neo-sprint-mm12-wall3 | True | 1.5074596428787017 | +| neo | neo | sprint | neo-neo-sprint-mm12-wall4 | True | 0.5074596428787022 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap3-ceil4p0 | True | 0.7711758286886656 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap4-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap2-ceil3p0 | True | 1.4715224031404017 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap3-ceil3p0 | True | 0.4715224031404017 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap4-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap1-ceil2p5 | True | 1.2809562314414416 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap2-ceil2p5 | True | 0.28095623144144133 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap1-ceil2p0 | True | 0.39341340402481095 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap1-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm0-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap3-ceil4p0 | True | 1.2874110562665573 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap4-ceil4p0 | True | 0.2874110562665573 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap3-ceil3p0 | True | 0.9566372864603423 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap4-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap2-ceil2p5 | True | 0.6119173623355341 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap1-ceil2p0 | True | 0.5750953286829639 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap1-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm1-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap3-ceil4p0 | True | 1.569275490524089 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap4-ceil4p0 | True | 0.5692754905240891 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap3-ceil3p0 | True | 1.2215100127530318 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap4-ceil3p0 | True | 0.22151001275303184 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap2-ceil2p5 | True | 0.7926221398037092 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap1-ceil2p0 | True | 0.6742936595463151 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap1-ceil1p8125 | True | 0.0067611897704629165 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm2-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap4-ceil4p0 | True | 0.7231734716286997 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap3-ceil3p0 | True | 1.3661305213088397 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap4-ceil3p0 | True | 0.3661305213088397 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap2-ceil2p5 | True | 0.891286948301333 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap1-ceil2p0 | True | 0.7284559481977051 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap1-ceil1p8125 | True | 0.03100044201483798 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm3-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap4-ceil4p0 | True | 0.807201769311817 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap3-ceil3p0 | True | 1.4450933189803106 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap4-ceil3p0 | True | 0.44509331898031057 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap2-ceil2p5 | True | 0.9451579337410356 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap1-ceil2p0 | True | 0.7580285578013639 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap1-ceil1p8125 | True | 0.044235073740266806 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm4-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap4-ceil4p0 | True | 0.8530812198467999 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap3-ceil3p0 | True | 1.488207006508934 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap4-ceil3p0 | True | 0.4882070065089339 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap2-ceil2p5 | True | 0.9745714917911132 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap1-ceil2p0 | True | 0.7741752026449618 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap1-ceil1p8125 | True | 0.05146118266235078 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm5-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap4-ceil4p0 | True | 0.8781313998389004 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap3-ceil3p0 | True | 1.5117470798995631 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap4-ceil3p0 | True | 0.5117470798995631 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap2-ceil2p5 | True | 0.9906312944864553 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap1-ceil2p0 | True | 0.7829912707295661 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap1-ceil1p8125 | True | 0.05540663813380875 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm6-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap4-ceil4p0 | True | 0.8918087981145861 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap3-ceil3p0 | True | 1.5245999599708453 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap4-ceil3p0 | True | 0.5245999599708453 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap2-ceil2p5 | True | 0.9993999467581123 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap3-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap1-ceil2p0 | True | 0.7878048439037599 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap1-ceil1p8125 | True | 0.05756085682122469 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm7-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap4-ceil4p0 | True | 0.8992766575731119 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap3-ceil3p0 | True | 1.5316176324897661 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap4-ceil3p0 | True | 0.5316176324897661 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap2-ceil2p5 | True | 1.0041876308984365 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap3-ceil2p5 | True | 0.004187630898436545 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap1-ceil2p0 | True | 0.7904330548568697 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap1-ceil1p8125 | True | 0.05873706022455383 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm8-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap4-ceil4p0 | True | 0.9033541088374646 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap3-ceil3p0 | True | 1.5354492816850955 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap4-ceil3p0 | True | 0.5354492816850955 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap2-ceil2p5 | True | 1.0068017064390542 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap3-ceil2p5 | True | 0.0068017064390542 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap1-ceil2p0 | True | 0.7918680580372677 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap1-ceil1p8125 | True | 0.05937926728277154 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm9-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap4-ceil4p0 | True | 0.9055803972278049 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap3-ceil3p0 | True | 1.537541362145748 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap4-ceil3p0 | True | 0.5375413621457481 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap2-ceil2p5 | True | 1.0082289916842315 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap3-ceil2p5 | True | 0.008228991684231524 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap1-ceil2p0 | True | 0.7926515697737653 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap1-ceil1p8125 | True | 0.059729912336558444 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm10-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap4-ceil4p0 | True | 0.9067959506889283 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap3-ceil3p0 | True | 1.5386836380772628 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap4-ceil3p0 | True | 0.5386836380772628 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap2-ceil2p5 | True | 1.0090082894280985 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap3-ceil2p5 | True | 0.009008289428098504 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap1-ceil2p0 | True | 0.793079367181893 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap1-ceil1p8125 | True | 0.059921364535926225 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm11-gap4-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap1-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap2-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap3-ceil4p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap4-ceil4p0 | True | 0.9074596428787016 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap1-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap2-ceil3p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap3-ceil3p0 | True | 1.5393073207358698 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap4-ceil3p0 | True | 0.5393073207358698 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap1-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap2-ceil2p5 | True | 1.009433785996249 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap3-ceil2p5 | True | 0.009433785996249 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap4-ceil2p5 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap1-ceil2p0 | True | 0.7933129445667304 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap2-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap3-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap4-ceil2p0 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap1-ceil1p8125 | True | 0.060025897436780884 | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap2-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap3-ceil1p8125 | False | None | +| ceiling | headhitter | sprint | ceiling-headhitter-sprint-mm12-gap4-ceil1p8125 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap1-dy0p0-wo0 | True | 1.289507369982936 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap1-dy1p0-wo0 | True | 0.7258590718663482 | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap2-dy0p0-wo0 | True | 0.2895073699829358 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap2-dym1p0-wo0 | True | 0.6624809325550243 | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap2-dym2p0-wo0 | True | 0.969858896301496 | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap3-dym2p0-wo0 | False | 0.004523378272422551 | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap1-dy0p0-wo1 | True | 1.289507369982936 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap1-dy1p0-wo1 | True | 0.7258590718663482 | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap2-dy0p0-wo1 | True | 0.2895073699829358 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap2-dym1p0-wo1 | True | 0.6624809325550243 | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap2-dym2p0-wo1 | True | 0.969858896301496 | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap3-dym2p0-wo1 | False | 0.004523378272422551 | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm0-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm0-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm0-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap1-dy1p0-wo0 | True | 1.1409836836694078 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap2-dy0p0-wo0 | True | 0.7978998244796651 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap2-dy1p0-wo0 | True | 0.1409836836694076 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap2-dym1p0-wo0 | True | 1.2171585696143596 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap3-dym1p0-wo0 | True | 0.21715856961435964 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap3-dym2p0-wo0 | True | 0.5560188899717717 | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap1-dy1p0-wo1 | True | 1.1409836836694078 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap2-dy0p0-wo1 | True | 0.7978998244796651 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap2-dy1p0-wo1 | True | 0.1409836836694076 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap2-dym1p0-wo1 | True | 1.2171585696143596 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap3-dym1p0-wo1 | True | 0.21715856961435964 | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap3-dym2p0-wo1 | True | 0.5560188899717717 | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm1-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm1-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm1-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap1-dy1p0-wo0 | True | 1.367641721713879 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap2-dy0p0-wo0 | True | 1.0754821046348813 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap2-dy1p0-wo0 | True | 0.36764172171387877 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap2-dym1p0-wo0 | True | 1.5200125594487588 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap3-dy0p0-wo0 | True | 0.07548210463488125 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap3-dym1p0-wo0 | True | 0.5200125594487588 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap3-dym2p0-wo0 | True | 0.8760622465157439 | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap1-dy1p0-wo1 | True | 1.367641721713879 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap2-dy0p0-wo1 | True | 1.0754821046348813 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap2-dy1p0-wo1 | True | 0.36764172171387877 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap2-dym1p0-wo1 | True | 1.5200125594487588 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap3-dy0p0-wo1 | True | 0.07548210463488125 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap3-dym1p0-wo1 | True | 0.5200125594487588 | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap3-dym2p0-wo1 | True | 0.8760622465157439 | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm2-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm2-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm2-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap1-dy1p0-wo0 | True | 1.4913970104861607 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap2-dy0p0-wo0 | True | 1.227042029599629 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap2-dy1p0-wo0 | True | 0.49139701048616047 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap3-dy0p0-wo0 | True | 0.22704202959962894 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap3-dym1p0-wo0 | True | 0.6853708378983403 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap3-dym2p0-wo0 | True | 1.050805919188753 | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap4-dym2p0-wo0 | True | 0.0508059191887531 | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap1-dy1p0-wo1 | True | 1.4913970104861607 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap2-dy0p0-wo1 | True | 1.227042029599629 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap2-dy1p0-wo1 | True | 0.49139701048616047 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap3-dy0p0-wo1 | True | 0.22704202959962894 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap3-dym1p0-wo1 | True | 0.6853708378983403 | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap3-dym2p0-wo1 | True | 1.050805919188753 | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap4-dym2p0-wo1 | True | 0.0508059191887531 | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm3-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm3-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm3-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap1-dy1p0-wo0 | True | 1.5589673981558259 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap2-dy0p0-wo0 | True | 1.3097937486303808 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap2-dy1p0-wo0 | True | 0.5589673981558256 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap3-dy0p0-wo0 | True | 0.3097937486303808 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap3-dym1p0-wo0 | True | 0.7756564579318108 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap3-dym2p0-wo0 | True | 1.1462159644682144 | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap4-dym2p0-wo0 | True | 0.1462159644682144 | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap1-dy1p0-wo1 | True | 1.5589673981558259 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap2-dy0p0-wo1 | True | 1.3097937486303808 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap2-dy1p0-wo1 | True | 0.5589673981558256 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap3-dy0p0-wo1 | True | 0.3097937486303808 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap3-dym1p0-wo1 | True | 0.7756564579318108 | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap3-dym2p0-wo1 | True | 1.1462159644682144 | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap4-dym2p0-wo1 | True | 0.1462159644682144 | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm4-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm4-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm4-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap2-dy0p0-wo0 | True | 1.3549761872211716 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap2-dy1p0-wo0 | True | 0.5958608298234633 | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap3-dy0p0-wo0 | True | 0.35497618722117164 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap3-dym1p0-wo0 | True | 0.8249524064700866 | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap3-dym2p0-wo0 | True | 1.1983098491908013 | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap4-dym2p0-wo0 | True | 0.1983098491908013 | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap2-dy0p0-wo1 | True | 1.3549761872211716 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap2-dy1p0-wo1 | True | 0.5958608298234633 | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap3-dy0p0-wo1 | True | 0.35497618722117164 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap3-dym1p0-wo1 | True | 0.8249524064700866 | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap3-dym2p0-wo1 | True | 1.1983098491908013 | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap4-dym2p0-wo1 | True | 0.1983098491908013 | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm5-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm5-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm5-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap2-dy0p0-wo0 | True | 1.379645798691743 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap2-dy1p0-wo0 | True | 0.6160046435139934 | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap3-dy0p0-wo0 | True | 0.379645798691743 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap3-dym1p0-wo0 | True | 0.8518679943719851 | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap3-dym2p0-wo0 | True | 1.2267531102493345 | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap4-dym2p0-wo0 | True | 0.22675311024933453 | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap2-dy0p0-wo1 | True | 1.379645798691743 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap2-dy1p0-wo1 | True | 0.6160046435139934 | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap3-dy0p0-wo1 | True | 0.379645798691743 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap3-dym1p0-wo1 | True | 0.8518679943719851 | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap3-dym2p0-wo1 | True | 1.2267531102493345 | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap4-dym2p0-wo1 | True | 0.22675311024933453 | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm6-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm6-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm6-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap2-dy0p0-wo0 | True | 1.393115406554676 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap2-dy1p0-wo0 | True | 0.6270031657890232 | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap3-dy0p0-wo0 | True | 0.39311540655467603 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap3-dym1p0-wo0 | True | 0.866563905366422 | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap3-dym2p0-wo0 | True | 1.2422831307872935 | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap4-dym2p0-wo0 | True | 0.24228313078729347 | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap2-dy0p0-wo1 | True | 1.393115406554676 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap2-dy1p0-wo1 | True | 0.6270031657890232 | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap3-dy0p0-wo1 | True | 0.39311540655467603 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap3-dym1p0-wo1 | True | 0.866563905366422 | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap3-dym2p0-wo1 | True | 1.2422831307872935 | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap4-dym2p0-wo1 | True | 0.24228313078729347 | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm7-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm7-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm7-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap2-dy0p0-wo0 | True | 1.4004698124478367 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap2-dy1p0-wo0 | True | 0.633008358951189 | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap3-dy0p0-wo0 | True | 0.40046981244783675 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap3-dym1p0-wo0 | True | 0.8745878727693839 | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap3-dym2p0-wo0 | True | 1.2507625220010183 | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap4-dym2p0-wo0 | True | 0.25076252200101834 | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap2-dy0p0-wo1 | True | 1.4004698124478367 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap2-dy1p0-wo1 | True | 0.633008358951189 | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap3-dy0p0-wo1 | True | 0.40046981244783675 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap3-dym1p0-wo1 | True | 0.8745878727693839 | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap3-dym2p0-wo1 | True | 1.2507625220010183 | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap4-dym2p0-wo1 | True | 0.25076252200101834 | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm8-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm8-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm8-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap2-dy0p0-wo0 | True | 1.404485318065503 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap2-dy1p0-wo0 | True | 0.636287194417732 | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap3-dy0p0-wo0 | True | 0.40448531806550303 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap3-dym1p0-wo0 | True | 0.8789689589714023 | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap3-dym2p0-wo0 | True | 1.2553922696037132 | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap4-dym2p0-wo0 | True | 0.2553922696037132 | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap2-dy0p0-wo1 | True | 1.404485318065503 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap2-dy1p0-wo1 | True | 0.636287194417732 | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap3-dy0p0-wo1 | True | 0.40448531806550303 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap3-dym1p0-wo1 | True | 0.8789689589714023 | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap3-dym2p0-wo1 | True | 1.2553922696037132 | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap4-dym2p0-wo1 | True | 0.2553922696037132 | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm9-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm9-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm9-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap2-dy0p0-wo0 | True | 1.4066777841327482 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap2-dy1p0-wo0 | True | 0.6380774385824641 | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap3-dy0p0-wo0 | True | 0.40667778413274824 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap3-dym1p0-wo0 | True | 0.8813610320377032 | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap3-dym2p0-wo0 | True | 1.2579201117947836 | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap4-dym2p0-wo0 | True | 0.2579201117947836 | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap2-dy0p0-wo1 | True | 1.4066777841327482 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap2-dy1p0-wo1 | True | 0.6380774385824641 | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap3-dy0p0-wo1 | True | 0.40667778413274824 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap3-dym1p0-wo1 | True | 0.8813610320377032 | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap3-dym2p0-wo1 | True | 1.2579201117947836 | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap4-dym2p0-wo1 | True | 0.2579201117947836 | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm10-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm10-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm10-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap2-dy0p0-wo0 | True | 1.4078748706054638 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap2-dy1p0-wo0 | True | 0.6390549118964075 | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap3-dy0p0-wo0 | True | 0.4078748706054638 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap3-dym1p0-wo0 | True | 0.8826671039319036 | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap3-dym2p0-wo0 | True | 1.2593003136311074 | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap4-dym2p0-wo0 | True | 0.2593003136311074 | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap2-dy0p0-wo1 | True | 1.4078748706054638 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap2-dy1p0-wo1 | True | 0.6390549118964075 | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap3-dy0p0-wo1 | True | 0.4078748706054638 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap3-dym1p0-wo1 | True | 0.8826671039319036 | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap3-dym2p0-wo1 | True | 1.2593003136311074 | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap4-dym2p0-wo1 | True | 0.2593003136311074 | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm11-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm11-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm11-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap2-dy0p0-wo0 | True | 1.4085284798195667 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap2-dy1p0-wo0 | True | 0.639588612325821 | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap3-dy0p0-wo0 | True | 0.40852847981956675 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap3-dym1p0-wo0 | True | 0.8833802191861366 | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap3-dym2p0-wo0 | True | 1.2600539038337413 | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap4-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap4-dym2p0-wo0 | True | 0.2600539038337413 | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap2-dy0p0-wo1 | True | 1.4085284798195667 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap2-dy1p0-wo1 | True | 0.639588612325821 | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap3-dy0p0-wo1 | True | 0.40852847981956675 | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap3-dym1p0-wo1 | True | 0.8833802191861366 | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap3-dym2p0-wo1 | True | 1.2600539038337413 | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap4-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap4-dym2p0-wo1 | True | 0.2600539038337413 | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | walk | sidewall-flat-walk-mm12-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | walk | sidewall-ascend-walk-mm12-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | walk | sidewall-descend-walk-mm12-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap2-dy1p0-wo0 | True | 0.8951266201794543 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap3-dy0p0-wo0 | True | 0.7214793175587331 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap3-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap3-dym1p0-wo0 | True | 1.2248227989442055 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap4-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap4-dym1p0-wo0 | True | 0.22482279894420554 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap4-dym2p0-wo0 | True | 0.6208760589341509 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap5-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap2-dy1p0-wo1 | True | 0.8951266201794543 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap3-dy0p0-wo1 | True | 0.7214793175587331 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap3-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap3-dym1p0-wo1 | True | 1.2248227989442055 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap4-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap4-dym1p0-wo1 | True | 0.22482279894420554 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap4-dym2p0-wo1 | True | 0.6208760589341509 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap5-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm0-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm0-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm0-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap2-dy1p0-wo0 | True | 1.3102512319825146 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap3-dy0p0-wo0 | True | 1.2298717720554633 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap3-dy1p0-wo0 | True | 0.3102512319825146 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap4-dy0p0-wo0 | True | 0.22987177205546327 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap4-dym1p0-wo0 | True | 0.7795004360035431 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap4-dym2p0-wo0 | True | 1.207036052604428 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap5-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap5-dym2p0-wo0 | True | 0.20703605260442792 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap2-dy1p0-wo1 | True | 1.3102512319825146 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap3-dy0p0-wo1 | True | 1.2298717720554633 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap3-dy1p0-wo1 | True | 0.3102512319825146 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap4-dy0p0-wo1 | True | 0.22987177205546327 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap4-dym1p0-wo1 | True | 0.7795004360035431 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap4-dym2p0-wo1 | True | 1.207036052604428 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap5-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap5-dym2p0-wo1 | True | 0.20703605260442792 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm1-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm1-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm1-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap2-dy1p0-wo0 | True | 1.5369092700269853 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap3-dy0p0-wo0 | True | 1.5074540522106785 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap3-dy1p0-wo0 | True | 0.5369092700269853 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap4-dy0p0-wo0 | True | 0.5074540522106785 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap4-dym1p0-wo0 | True | 1.0823544258379405 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap5-dym1p0-wo0 | True | 0.0823544258379405 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap5-dym2p0-wo0 | True | 0.5270794091483992 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap2-dy1p0-wo1 | True | 1.5369092700269853 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap3-dy0p0-wo1 | True | 1.5074540522106785 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap3-dy1p0-wo1 | True | 0.5369092700269853 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap4-dy0p0-wo1 | True | 0.5074540522106785 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap4-dym1p0-wo1 | True | 1.0823544258379405 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap5-dym1p0-wo1 | True | 0.0823544258379405 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap5-dym2p0-wo1 | True | 0.5270794091483992 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm2-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm2-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm2-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap3-dy1p0-wo0 | True | 0.6606645587992661 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap4-dy0p0-wo0 | True | 0.6590139771754258 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap4-dym1p0-wo0 | True | 1.2477127042875216 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap5-dym1p0-wo0 | True | 0.24771270428752157 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap5-dym2p0-wo0 | True | 0.7018230818214075 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap3-dy1p0-wo1 | True | 0.6606645587992661 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap4-dy0p0-wo1 | True | 0.6590139771754258 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap4-dym1p0-wo1 | True | 1.2477127042875216 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap5-dym1p0-wo1 | True | 0.24771270428752157 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap5-dym2p0-wo1 | True | 0.7018230818214075 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm3-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm3-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm3-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap3-dy1p0-wo0 | True | 0.7282349464689326 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap4-dy0p0-wo0 | True | 0.7417656962061789 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap4-dym1p0-wo0 | True | 1.3379983243209939 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap5-dym1p0-wo0 | True | 0.33799832432099386 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap5-dym2p0-wo0 | True | 0.7972331271008715 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap3-dy1p0-wo1 | True | 0.7282349464689326 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap4-dy0p0-wo1 | True | 0.7417656962061789 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap4-dym1p0-wo1 | True | 1.3379983243209939 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap5-dym1p0-wo1 | True | 0.33799832432099386 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap5-dym2p0-wo1 | True | 0.7972331271008715 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm4-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm4-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm4-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap3-dy1p0-wo0 | True | 0.7651283781365694 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap4-dy0p0-wo0 | True | 0.7869481347969689 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap4-dym1p0-wo0 | True | 1.3872942728592683 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap5-dym1p0-wo0 | True | 0.3872942728592683 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap5-dym2p0-wo0 | True | 0.8493270118234566 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap3-dy1p0-wo1 | True | 0.7651283781365694 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap4-dy0p0-wo1 | True | 0.7869481347969689 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap4-dym1p0-wo1 | True | 1.3872942728592683 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap5-dym1p0-wo1 | True | 0.3872942728592683 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap5-dym2p0-wo1 | True | 0.8493270118234566 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm5-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm5-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm5-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap3-dy1p0-wo0 | True | 0.7852721918270991 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap4-dy0p0-wo0 | True | 0.8116177462675402 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap4-dym1p0-wo0 | True | 1.4142098607611668 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap5-dym1p0-wo0 | True | 0.41420986076116684 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap5-dym2p0-wo0 | True | 0.8777702728819898 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap3-dy1p0-wo1 | True | 0.7852721918270991 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap4-dy0p0-wo1 | True | 0.8116177462675402 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap4-dym1p0-wo1 | True | 1.4142098607611668 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap5-dym1p0-wo1 | True | 0.41420986076116684 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap5-dym2p0-wo1 | True | 0.8777702728819898 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm6-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm6-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm6-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap3-dy1p0-wo0 | True | 0.7962707141021292 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap4-dy0p0-wo0 | True | 0.8250873541304724 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap4-dym1p0-wo0 | True | 1.4289057717556028 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap5-dym1p0-wo0 | True | 0.42890577175560285 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap5-dym2p0-wo0 | True | 0.893300293419947 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap3-dy1p0-wo1 | True | 0.7962707141021292 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap4-dy0p0-wo1 | True | 0.8250873541304724 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap4-dym1p0-wo1 | True | 1.4289057717556028 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap5-dym1p0-wo1 | True | 0.42890577175560285 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap5-dym2p0-wo1 | True | 0.893300293419947 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm7-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm7-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm7-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap3-dy1p0-wo0 | True | 0.8022759072642955 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap4-dy0p0-wo0 | True | 0.8324417600236336 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap4-dym1p0-wo0 | True | 1.4369297391585656 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap5-dym1p0-wo0 | True | 0.4369297391585656 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap5-dym2p0-wo0 | True | 0.9017796846336736 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap3-dy1p0-wo1 | True | 0.8022759072642955 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap4-dy0p0-wo1 | True | 0.8324417600236336 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap4-dym1p0-wo1 | True | 1.4369297391585656 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap5-dym1p0-wo1 | True | 0.4369297391585656 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap5-dym2p0-wo1 | True | 0.9017796846336736 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm8-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm8-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm8-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap3-dy1p0-wo0 | True | 0.8055547427308376 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap4-dy0p0-wo0 | True | 0.8364572656413003 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap4-dym1p0-wo0 | True | 1.441310825360584 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap5-dym1p0-wo0 | True | 0.44131082536058397 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap5-dym2p0-wo0 | True | 0.9064094322363685 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap3-dy1p0-wo1 | True | 0.8055547427308376 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap4-dy0p0-wo1 | True | 0.8364572656413003 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap4-dym1p0-wo1 | True | 1.441310825360584 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap5-dym1p0-wo1 | True | 0.44131082536058397 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap5-dym2p0-wo1 | True | 0.9064094322363685 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm9-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm9-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm9-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap3-dy1p0-wo0 | True | 0.8073449868955702 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap4-dy0p0-wo0 | True | 0.8386497317085455 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap4-dym1p0-wo0 | True | 1.4437028984268858 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap5-dym1p0-wo0 | True | 0.4437028984268858 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap5-dym2p0-wo0 | True | 0.9089372744274398 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap3-dy1p0-wo1 | True | 0.8073449868955702 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap4-dy0p0-wo1 | True | 0.8386497317085455 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap4-dym1p0-wo1 | True | 1.4437028984268858 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap5-dym1p0-wo1 | True | 0.4437028984268858 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap5-dym2p0-wo1 | True | 0.9089372744274398 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm10-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm10-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm10-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap3-dy1p0-wo0 | True | 0.8083224602095145 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap4-dy0p0-wo0 | True | 0.839846818181262 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap4-dym1p0-wo0 | True | 1.445008970321087 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap5-dym1p0-wo0 | True | 0.4450089703210871 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap5-dym2p0-wo0 | True | 0.9103174762637654 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap3-dy1p0-wo1 | True | 0.8083224602095145 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap4-dy0p0-wo1 | True | 0.839846818181262 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap4-dym1p0-wo1 | True | 1.445008970321087 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap5-dym1p0-wo1 | True | 0.4450089703210871 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap5-dym2p0-wo1 | True | 0.9103174762637654 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm11-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm11-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm11-gap7-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap0-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap0-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap0-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap0-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap1-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap1-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap1-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap1-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap2-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap2-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap2-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap2-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap3-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap3-dy1p0-wo0 | True | 0.8088561606389266 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap3-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap3-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap4-dy0p0-wo0 | True | 0.8405004273953631 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap4-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap4-dym1p0-wo0 | True | 1.4457220855753175 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap4-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap5-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap5-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap5-dym1p0-wo0 | True | 0.44572208557531745 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap5-dym2p0-wo0 | True | 0.9110710664663948 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap6-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap6-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap6-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap6-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap7-dy0p0-wo0 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap7-dy1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap7-dym1p0-wo0 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap7-dym2p0-wo0 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap0-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap0-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap0-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap0-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap1-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap1-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap1-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap1-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap2-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap2-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap2-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap2-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap3-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap3-dy1p0-wo1 | True | 0.8088561606389266 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap3-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap3-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap4-dy0p0-wo1 | True | 0.8405004273953631 | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap4-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap4-dym1p0-wo1 | True | 1.4457220855753175 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap4-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap5-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap5-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap5-dym1p0-wo1 | True | 0.44572208557531745 | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap5-dym2p0-wo1 | True | 0.9110710664663948 | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap6-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap6-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap6-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap6-dym2p0-wo1 | False | None | +| sidewall | flat | sprint | sidewall-flat-sprint-mm12-gap7-dy0p0-wo1 | False | None | +| sidewall | ascend | sprint | sidewall-ascend-sprint-mm12-gap7-dy1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap7-dym1p0-wo1 | False | None | +| sidewall | descend | sprint | sidewall-descend-sprint-mm12-gap7-dym2p0-wo1 | False | None | diff --git a/tools/pathing_live_common.sh b/tools/pathing_live_common.sh new file mode 100644 index 0000000000..d484cc1280 --- /dev/null +++ b/tools/pathing_live_common.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +mcc_cmd_live() { + if [[ -n "${SESSION:-}" ]]; then + mcc-cmd --session "$SESSION" "$1" + else + mcc-cmd "$1" + fi +} + +manifest_cases_for_query() { + local manifest_path="$1" + local family_csv="$2" + + python3 - "$manifest_path" "$family_csv" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as handle: + manifest = json.load(handle) + +families = {item for item in sys.argv[2].split(",") if item} +for row in manifest: + if row["family"] in families and row["movement_mode"] == "sprint" and row["momentum_ticks"] == 12: + print(row["case_id"]) +PY +} + +manifest_case_json() { + local manifest_path="$1" + local case_id="$2" + + python3 - "$manifest_path" "$case_id" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as handle: + manifest = json.load(handle) + +case_id = sys.argv[2] +row = next(row for row in manifest if row["case_id"] == case_id) +print(json.dumps(row)) +PY +} + +record_live_result() { + local results_path="$1" + local case_json="$2" + local live_result="$3" + local log_path="$4" + + python3 - "$results_path" "$case_json" "$live_result" "$log_path" <<'PY' +import json +import sys + +row = json.loads(sys.argv[2]) +record = { + "case_id": row["case_id"], + "bucket_id": row["bucket_id"], + "world_recipe_id": row["world_recipe_id"], + "expected_result": row["expected_result"], + "live_result": sys.argv[3], + "log_path": sys.argv[4], +} + +with open(sys.argv[1], "a", encoding="utf-8") as handle: + handle.write(json.dumps(record) + "\n") +PY +} + +run_test() { + local name="$1" + local start_x="$2" start_y="$3" start_z="$4" + local dest_x="$5" dest_y="$6" dest_z="$7" + local username="${USERNAME:-MCCBot}" + + TEST_NUM=$((TEST_NUM + 1)) + echo "" + echo "=== TEST $TEST_NUM: $name ===" + echo " Start: ($start_x, $start_y, $start_z) -> Dest: ($dest_x, $dest_y, $dest_z)" + + mcc_cmd_live "respawn" 2>/dev/null || true + sleep 0.5 + mc-rcon "gamemode creative $username" >/dev/null 2>&1 + sleep 0.3 + mc-rcon "tp $username ${start_x}.5 ${start_y} ${start_z}.5" >/dev/null 2>&1 + sleep 2 + mc-rcon "gamemode survival $username" >/dev/null 2>&1 + sleep 1 + + : > "$LOG" + sleep 0.5 + + mcc_cmd_live "pathfind $dest_x $dest_y $dest_z" + sleep 8 + + local a_star_result + a_star_result=$(grep -a '\[A\*\]' "$LOG" | head -3 | sed 's/\x1b\[[0-9;]*m//g' || true) + + local path_exec + path_exec=$(grep -a '\[PathExec\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g' || true) + + local path_mgr + path_mgr=$(grep -a '\[PathMgr\]' "$LOG" | sed 's/\x1b\[[0-9;]*m//g' || true) + + local nav_segs + nav_segs=$(grep -a '\[Navigate\].*seg' "$LOG" | sed 's/\x1b\[[0-9;]*m//g' || true) + + local physics_line + physics_line=$(grep -a '\[Physics\]' "$LOG" | tail -1 | sed 's/\x1b\[[0-9;]*m//g' || true) + + local result="invalid_live_case" + if echo "$path_mgr" | grep -q "complete"; then + result="pass" + elif echo "$a_star_result" | grep -q "Failed"; then + result="reject" + elif echo "$path_mgr" | grep -q "Replan failed\|Giving up"; then + result="fail" + elif echo "$path_exec" | grep -q "FAILED"; then + result="fail" + fi + LAST_RESULT="$result" + + echo " A*: $a_star_result" + echo " Segments: $nav_segs" + echo " Exec: $(echo "$path_exec" | tail -3)" + echo " Manager: $(echo "$path_mgr" | tail -2)" + echo " Physics: $physics_line" + echo " RESULT: $LAST_RESULT" + + RESULTS="${RESULTS}TEST $TEST_NUM ($name): $LAST_RESULT\n" +} diff --git a/tools/pathing_theory/__init__.py b/tools/pathing_theory/__init__.py new file mode 100644 index 0000000000..befbaa3829 --- /dev/null +++ b/tools/pathing_theory/__init__.py @@ -0,0 +1 @@ +"""Reusable theory generation helpers for pathing analysis tools.""" diff --git a/tools/pathing_theory/canonical.py b/tools/pathing_theory/canonical.py new file mode 100644 index 0000000000..883b8d67e3 --- /dev/null +++ b/tools/pathing_theory/canonical.py @@ -0,0 +1,124 @@ +from tools.pathing_theory.models import CanonicalLiveCase, TheoryCase + + +def _world_recipe_id(case: TheoryCase) -> str: + if case.family == "linear": + return f"linear-{case.subfamily}" + if case.family == "neo": + return "neo-wall" + if case.family == "sidewall": + return f"sidewall-{case.subfamily}" + return "ceiling-headhitter" + + +def _canonical_goal(case: TheoryCase) -> tuple[dict[str, float], dict[str, float]]: + start = {"x": 100.5, "y": 80.0, "z": 100.5} + if case.family == "linear": + goal_y = 80.0 + (case.delta_y or 0.0) + goal_x = 100 + (case.gap_blocks or 0) + 1 + return start, {"x": float(goal_x), "y": goal_y, "z": 100.0} + if case.family == "neo": + goal_z = 100 + (case.wall_width or 1) + return start, {"x": 102.0, "y": 80.0, "z": float(goal_z)} + if case.family == "sidewall": + goal_y = 80.0 + (case.delta_y or 0.0) + goal_x = 100 + (case.gap_blocks or 0) + 1 + wall_z_offset = 100 + 1 + (case.wall_offset or 0) + return ( + {"x": 100.5, "y": 80.0, "z": 100.5}, + {"x": float(goal_x), "y": goal_y, "z": 100.0}, + ) + goal_x = 100 + (case.gap_blocks or 0) + 1 + return start, {"x": float(goal_x), "y": 80.0, "z": 100.0} + + +def _select_boundary_case( + family: str, + subfamily: str, + reachable: list[TheoryCase], +) -> TheoryCase: + if family in {"linear", "sidewall"}: + preferred_gap_by_subfamily = { + "flat": 5, + "ascend": 2, + "descend": 2, + } + preferred_gap = preferred_gap_by_subfamily.get(subfamily) + if preferred_gap is not None: + for case in reachable: + if case.gap_blocks == preferred_gap: + return case + if family == "ceiling" and subfamily == "headhitter": + for case in reachable: + if case.gap_blocks == 3 and case.ceiling_height == 2.0: + return case + return reachable[0] + + +def build_canonical_live_cases(cases: list[TheoryCase]) -> list[CanonicalLiveCase]: + live_candidate_cases = [ + case + for case in cases + if case.movement_mode == "sprint" and case.momentum_ticks == 12 + ] + + by_bucket: dict[tuple[str, str, str], list[TheoryCase]] = {} + for case in live_candidate_cases: + by_bucket.setdefault( + (case.family, case.subfamily, case.movement_mode), + [], + ).append(case) + + canonical_cases: list[CanonicalLiveCase] = [] + for family, subfamily, movement_mode in sorted(by_bucket): + bucket_cases = by_bucket[(family, subfamily, movement_mode)] + reachable = sorted( + [ + case + for case in bucket_cases + if case.expected_reachable and case.margin is not None + ], + key=lambda case: case.margin, + ) + unreachable = sorted( + [case for case in bucket_cases if not case.expected_reachable], + key=lambda case: float("-inf") if case.margin is None else abs(case.margin), + ) + + selected: list[tuple[str, TheoryCase]] = [] + if reachable: + easy = next( + (case for case in reversed(reachable) if (case.margin or 0.0) >= 0.50), + reachable[-1], + ) + boundary = _select_boundary_case(family, subfamily, reachable) + selected.append(("easy", easy)) + if boundary.case_id != easy.case_id: + selected.append(("boundary", boundary)) + if unreachable: + selected.append(("reject", unreachable[0])) + + for difficulty_band, case in selected: + start, goal = _canonical_goal(case) + canonical_cases.append( + CanonicalLiveCase( + case_id=case.case_id, + bucket_id=f"{family}:{subfamily}:{movement_mode}:{difficulty_band}", + family=family, + subfamily=subfamily, + movement_mode=movement_mode, + momentum_ticks=case.momentum_ticks, + difficulty_band=difficulty_band, + expected_result="pass" if case.expected_reachable else "reject", + world_recipe_id=_world_recipe_id(case), + gap_blocks=case.gap_blocks, + delta_y=case.delta_y, + ceiling_height=case.ceiling_height, + wall_width=case.wall_width, + wall_offset=case.wall_offset, + start=start, + goal=goal, + ) + ) + + return canonical_cases diff --git a/tools/pathing_theory/capabilities.py b/tools/pathing_theory/capabilities.py new file mode 100644 index 0000000000..323e4374cb --- /dev/null +++ b/tools/pathing_theory/capabilities.py @@ -0,0 +1,200 @@ +from collections import defaultdict + +from tools.pathing_theory.models import MomentumCapabilityBand, TheoryCase + + +def _linear_group_key(case: TheoryCase) -> tuple[object, ...]: + return ( + case.family, + case.subfamily, + case.movement_mode, + "gap_blocks", + case.delta_y, + None, + None, + ) + + +def _neo_group_key(case: TheoryCase) -> tuple[object, ...]: + return ( + case.family, + case.subfamily, + case.movement_mode, + "wall_width", + None, + None, + None, + ) + + +def _ceiling_group_key(case: TheoryCase) -> tuple[object, ...]: + return ( + case.family, + case.subfamily, + case.movement_mode, + "gap_blocks", + None, + case.ceiling_height, + None, + ) + + +def _sidewall_group_key(case: TheoryCase) -> tuple[object, ...]: + return ( + case.family, + case.subfamily, + case.movement_mode, + "gap_blocks", + case.delta_y, + None, + case.wall_offset, + ) + + +def _case_group_key(case: TheoryCase) -> tuple[object, ...]: + if case.family == "linear": + return _linear_group_key(case) + if case.family == "neo": + return _neo_group_key(case) + if case.family == "ceiling": + return _ceiling_group_key(case) + if case.family == "sidewall": + return _sidewall_group_key(case) + raise ValueError(f"Unsupported theory family for capability bands: {case.family}") + + +def _case_reach_value(case: TheoryCase) -> int | None: + if not case.expected_reachable: + return None + if case.family in {"linear", "ceiling", "sidewall"}: + return case.gap_blocks + if case.family == "neo": + return case.wall_width + raise ValueError(f"Unsupported theory family for capability bands: {case.family}") + + +def _compress_mm_ranges( + mm_to_reach: list[tuple[int, int | None]], + family: str, + subfamily: str, + movement_mode: str, + capability_metric: str, + delta_y: float | None, + ceiling_height: float | None, + wall_offset: int | None = None, +) -> list[MomentumCapabilityBand]: + bands: list[MomentumCapabilityBand] = [] + current_start = mm_to_reach[0][0] + current_end = current_start + current_reach = mm_to_reach[0][1] + + for mm, max_reach in mm_to_reach[1:]: + if max_reach == current_reach and mm == current_end + 1: + current_end = mm + continue + + bands.append( + MomentumCapabilityBand( + family=family, + subfamily=subfamily, + movement_mode=movement_mode, + capability_metric=capability_metric, + min_mm=current_start, + max_mm=current_end, + max_reach=current_reach, + delta_y=delta_y, + ceiling_height=ceiling_height, + wall_offset=wall_offset, + ) + ) + current_start = mm + current_end = mm + current_reach = max_reach + + bands.append( + MomentumCapabilityBand( + family=family, + subfamily=subfamily, + movement_mode=movement_mode, + capability_metric=capability_metric, + min_mm=current_start, + max_mm=current_end, + max_reach=current_reach, + delta_y=delta_y, + ceiling_height=ceiling_height, + wall_offset=wall_offset, + ) + ) + return bands + + +def build_momentum_capability_bands( + cases: list[TheoryCase], +) -> list[MomentumCapabilityBand]: + grouped_cases: dict[tuple[object, ...], list[TheoryCase]] = defaultdict(list) + for case in cases: + grouped_cases[_case_group_key(case)].append(case) + + bands: list[MomentumCapabilityBand] = [] + for key in sorted(grouped_cases): + family, subfamily, movement_mode, capability_metric, delta_y, ceiling_height, wall_offset = key + mm_groups: dict[int, list[TheoryCase]] = defaultdict(list) + for case in grouped_cases[key]: + mm_groups[case.momentum_ticks].append(case) + + mm_to_reach = [ + ( + mm, + max( + ( + _case_reach_value(case) + for case in mm_groups[mm] + if _case_reach_value(case) is not None + ), + default=None, + ), + ) + for mm in sorted(mm_groups) + ] + bands.extend( + _compress_mm_ranges( + mm_to_reach=mm_to_reach, + family=family, + subfamily=subfamily, + movement_mode=movement_mode, + capability_metric=capability_metric, + delta_y=delta_y, + ceiling_height=ceiling_height, + wall_offset=wall_offset, + ) + ) + + return bands + + +def _format_range(band: MomentumCapabilityBand) -> str: + return f"{band.min_mm}..{band.max_mm}" + + +def _format_reach(band: MomentumCapabilityBand) -> str: + label = "max_gap" if band.capability_metric == "gap_blocks" else "max_wall_width" + value = "none" if band.max_reach is None else str(band.max_reach) + return f"{label}={value}" + + +def format_momentum_capability_lines( + bands: list[MomentumCapabilityBand], +) -> list[str]: + lines: list[str] = [] + for band in bands: + parts = [band.family, band.subfamily, band.movement_mode] + if band.delta_y is not None: + parts.append(f"dy={band.delta_y}") + if band.ceiling_height is not None: + parts.append(f"ceil={band.ceiling_height}") + if band.wall_offset is not None: + parts.append(f"wo={band.wall_offset}") + parts.append(f"mm={_format_range(band)}") + parts.append(_format_reach(band)) + lines.append(" | ".join(parts)) + return lines diff --git a/tools/pathing_theory/models.py b/tools/pathing_theory/models.py new file mode 100644 index 0000000000..f5d0349396 --- /dev/null +++ b/tools/pathing_theory/models.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class TheoryCase: + case_id: str + family: str + subfamily: str + movement_mode: str + momentum_ticks: int + gap_blocks: int | None + delta_y: float | None + ceiling_height: float | None + wall_width: int | None + wall_offset: int | None + expected_reachable: bool + landing_x: float | None + apex_y: float | None + margin: float | None + notes: str = "" + + +@dataclass(frozen=True) +class MomentumCapabilityBand: + family: str + subfamily: str + movement_mode: str + capability_metric: str + min_mm: int + max_mm: int + max_reach: int | None + delta_y: float | None = None + ceiling_height: float | None = None + wall_offset: int | None = None + notes: str = "" + + +@dataclass(frozen=True) +class CanonicalLiveCase: + case_id: str + bucket_id: str + family: str + subfamily: str + movement_mode: str + momentum_ticks: int + difficulty_band: str + expected_result: str + world_recipe_id: str + gap_blocks: int | None + delta_y: float | None + ceiling_height: float | None + wall_width: int | None + wall_offset: int | None + start: dict[str, float] + goal: dict[str, float] diff --git a/tools/pathing_theory/primitives.py b/tools/pathing_theory/primitives.py new file mode 100644 index 0000000000..d1bf216a62 --- /dev/null +++ b/tools/pathing_theory/primitives.py @@ -0,0 +1,442 @@ +import math +from dataclasses import dataclass +from typing import Optional + +PLAYER_WIDTH = 0.6 +PLAYER_HEIGHT = 1.8 +STEP_HEIGHT = 0.6 + +GRAVITY = 0.08 +DRAG_Y = 0.98 +FRICTION_MULTIPLIER = 0.91 +DEFAULT_BLOCK_FRICTION = 0.6 +INPUT_FRICTION = 0.98 +GROUND_ACCEL_FACTOR = 0.21600002 +AIR_ACCEL = 0.02 +MOVEMENT_SPEED = 0.1 + +BASE_JUMP_POWER = 0.42 +SPRINT_JUMP_HORIZONTAL_BOOST = 0.2 + +HORIZONTAL_VELOCITY_THRESHOLD_SQR = 9.0e-6 +VERTICAL_VELOCITY_THRESHOLD = 0.003 + +HALF_WIDTH = PLAYER_WIDTH / 2.0 +# Reliable late-jump timing is slightly before the full 0.8 block walk-off limit. +# Baritone uses a 0.7 threshold for the analogous 2-gap flat parkour execution. +EDGE_TAKEOFF_X = 0.7 +TARGET_BLOCK_WIDTH = 1.0 + + +@dataclass +class TickState: + tick: int = 0 + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + vx: float = 0.0 + vy: float = 0.0 + vz: float = 0.0 + on_ground: bool = True + + +def get_ground_speed(block_friction: float = DEFAULT_BLOCK_FRICTION) -> float: + friction = block_friction * FRICTION_MULTIPLIER + return MOVEMENT_SPEED * (GROUND_ACCEL_FACTOR / (friction * friction * friction)) + + +def build_momentum_velocity( + momentum_ticks: int, + block_friction: float = DEFAULT_BLOCK_FRICTION, +) -> float: + vx = 0.0 + ground_friction = block_friction * FRICTION_MULTIPLIER + for _ in range(momentum_ticks): + vx += INPUT_FRICTION * get_ground_speed(block_friction) + vx *= ground_friction + return vx + + +def build_momentum_velocity_2d( + momentum_ticks: int, + yaw_rad: float, + strafe_input: float = 0.0, + block_friction: float = DEFAULT_BLOCK_FRICTION, + wall_z: Optional[float] = None, + start_z: float = 0.0, +) -> tuple[float, float, float]: + """Build pre-jump velocity with yaw and optional strafe, returning (vx, vz, z). + + Simulates ground ticks before the jump edge, accounting for yaw-split + acceleration, optional strafe, and wall collision on the z axis. + """ + cos_yaw = math.cos(yaw_rad) + sin_yaw = math.sin(yaw_rad) + ground_friction = block_friction * FRICTION_MULTIPLIER + ground_speed = get_ground_speed(block_friction) + vx, vz, z = 0.0, 0.0, start_z + + for _ in range(momentum_ticks): + input_x = (cos_yaw + strafe_input * (-sin_yaw)) * INPUT_FRICTION + input_z = (sin_yaw + strafe_input * cos_yaw) * INPUT_FRICTION + vx += input_x * ground_speed + vz += input_z * ground_speed + + z += vz + if wall_z is not None and z + HALF_WIDTH > wall_z: + z = wall_z - HALF_WIDTH + if vz > 0: + vz = 0.0 + + vx *= ground_friction + vz *= ground_friction + + return vx, vz, z + + +def _get_overlap_window( + start_x: float, + end_x: float, + landing_x_start: float, + landing_width: Optional[float], +) -> Optional[tuple[float, float]]: + min_center_x = landing_x_start - HALF_WIDTH + max_center_x = ( + None if landing_width is None else landing_x_start + landing_width + HALF_WIDTH + ) + + if start_x > end_x: + start_x, end_x = end_x, start_x + + delta_x = end_x - start_x + if delta_x == 0.0: + if start_x < min_center_x: + return None + if max_center_x is not None and start_x > max_center_x: + return None + return 0.0, 1.0 + + if end_x < min_center_x: + return None + + enter_t = 0.0 if start_x >= min_center_x else (min_center_x - start_x) / delta_x + + if max_center_x is None: + exit_t = 1.0 + else: + if start_x > max_center_x: + return None + exit_t = 1.0 if end_x <= max_center_x else (max_center_x - start_x) / delta_x + + if exit_t < 0.0 or enter_t > 1.0 or enter_t > exit_t: + return None + + return max(0.0, enter_t), min(1.0, exit_t) + + +def _find_landing_contact( + start_x: float, + start_y: float, + end_x: float, + end_y: float, + landing_y: float, + landing_x_start: float, + landing_width: Optional[float], +) -> Optional[tuple[float, float]]: + if start_y < landing_y or end_y > landing_y or start_y == end_y: + return None + + overlap_window = _get_overlap_window( + start_x=start_x, + end_x=end_x, + landing_x_start=landing_x_start, + landing_width=landing_width, + ) + if overlap_window is None: + return None + + landing_t = (start_y - landing_y) / (start_y - end_y) + enter_t, exit_t = overlap_window + if landing_t < enter_t or landing_t > exit_t: + return None + + landing_x = start_x + (end_x - start_x) * landing_t + return landing_x, landing_y + + +def simulate_jump( + sprint: bool = True, + momentum_ticks: int = 12, + ceiling_y: Optional[float] = None, + landing_y: float = 0.0, + landing_x_start: float = 0.0, + landing_width: Optional[float] = None, + max_ticks: int = 200, + yaw_degrees: float = 0.0, + strafe_input: float = 0.0, + wall_z: Optional[float] = None, + start_z: float = 0.0, +) -> list[TickState]: + yaw_rad = math.radians(yaw_degrees) + cos_yaw = math.cos(yaw_rad) + sin_yaw = math.sin(yaw_rad) + has_lateral = yaw_degrees != 0.0 or strafe_input != 0.0 or wall_z is not None + + if has_lateral: + vx, vz, z = build_momentum_velocity_2d( + momentum_ticks, yaw_rad, strafe_input, + wall_z=wall_z, start_z=start_z, + ) + else: + vx = build_momentum_velocity(momentum_ticks) + vz = 0.0 + z = start_z + + x, y, vy = EDGE_TAKEOFF_X, 0.0, 0.0 + on_ground = True + trajectory: list[TickState] = [] + jumped = False + ground_friction = DEFAULT_BLOCK_FRICTION * FRICTION_MULTIPLIER + + trajectory.append(TickState(0, x, y, z, vx, vy, vz, on_ground)) + + for tick in range(1, max_ticks + 1): + if vx * vx + vz * vz < HORIZONTAL_VELOCITY_THRESHOLD_SQR: + vx = 0.0 + vz = 0.0 + if abs(vy) < VERTICAL_VELOCITY_THRESHOLD: + vy = 0.0 + + do_jump = False + if not jumped and on_ground: + do_jump = True + jumped = True + + if do_jump: + vy = max(BASE_JUMP_POWER, vy) + if sprint: + vx += SPRINT_JUMP_HORIZONTAL_BOOST * cos_yaw + vz += SPRINT_JUMP_HORIZONTAL_BOOST * sin_yaw + + speed = get_ground_speed() if on_ground else AIR_ACCEL + if has_lateral: + input_x = (cos_yaw + strafe_input * (-sin_yaw)) * INPUT_FRICTION + input_z = (sin_yaw + strafe_input * cos_yaw) * INPUT_FRICTION + vx += input_x * speed + vz += input_z * speed + else: + vx += INPUT_FRICTION * speed + + new_x = x + vx + new_y = y + vy + new_z = z + vz + new_on_ground = False + + if wall_z is not None and new_z + HALF_WIDTH > wall_z: + new_z = wall_z - HALF_WIDTH + if vz > 0: + vz = 0.0 + + if ceiling_y is not None: + head_y = new_y + PLAYER_HEIGHT + if head_y > ceiling_y: + new_y = ceiling_y - PLAYER_HEIGHT + if vy > 0: + vy = 0.0 + + if jumped: + contact = _find_landing_contact( + start_x=x, + start_y=y, + end_x=new_x, + end_y=new_y, + landing_y=landing_y, + landing_x_start=landing_x_start, + landing_width=landing_width, + ) + if contact is not None: + new_x, new_y = contact + vy = 0.0 + new_on_ground = True + + x = new_x + y = new_y + z = new_z + on_ground = new_on_ground + + vy -= GRAVITY + vy *= DRAG_Y + + if on_ground: + vx *= ground_friction + vz *= ground_friction + else: + vx *= FRICTION_MULTIPLIER + vz *= FRICTION_MULTIPLIER + + trajectory.append(TickState(tick, x, y, z, vx, vy, vz, on_ground)) + + if jumped and on_ground: + break + + return trajectory + + +def get_landing( + sprint: bool, + target_y: float, + landing_x_start: float = 0.0, + momentum_ticks: int = 12, + ceiling_y: Optional[float] = None, + landing_width: Optional[float] = None, + yaw_degrees: float = 0.0, + strafe_input: float = 0.0, + wall_z: Optional[float] = None, + start_z: float = 0.0, +) -> Optional[tuple[float, float]]: + trajectory = simulate_jump( + sprint=sprint, + momentum_ticks=momentum_ticks, + ceiling_y=ceiling_y, + landing_y=target_y, + landing_x_start=landing_x_start, + landing_width=landing_width, + yaw_degrees=yaw_degrees, + strafe_input=strafe_input, + wall_z=wall_z, + start_z=start_z, + ) + was_air = False + for state in trajectory: + if not state.on_ground: + was_air = True + if was_air and state.on_ground: + return state.x, state.y + return None + + +def get_apex( + sprint: bool, + momentum_ticks: int = 12, + ceiling_y: Optional[float] = None, +) -> tuple[float, float]: + trajectory = simulate_jump( + sprint=sprint, + momentum_ticks=momentum_ticks, + ceiling_y=ceiling_y, + landing_y=-1000.0, + landing_x_start=0.0, + max_ticks=300, + ) + best_y, best_x = 0.0, 0.0 + for state in trajectory: + if state.y > best_y: + best_y = state.y + best_x = state.x + return best_y, best_x + + +def can_reach_gap( + gap_blocks: int, + dy: float, + sprint: bool = True, + momentum_ticks: int = 12, +) -> tuple[bool, Optional[float], float]: + if dy > 1.252: + return False, None, 0.0 + + needed_x = 0.5 + gap_blocks - HALF_WIDTH + landing_platform_start = 0.5 + gap_blocks + + if gap_blocks == 0 and dy > 0: + landing_platform_start = 0.5 + + result = get_landing( + sprint=sprint, + target_y=dy, + landing_x_start=landing_platform_start, + momentum_ticks=momentum_ticks, + landing_width=TARGET_BLOCK_WIDTH, + ) + if result is None: + return False, None, needed_x + + landing_x, landing_y = result + if abs(landing_y - dy) > 0.01: + return False, landing_x, needed_x + + if gap_blocks > 0 and landing_x < needed_x: + return False, landing_x, needed_x + + return True, landing_x, needed_x + + +SIDE_WALL_YAW_SWEEP = [0.0, 3.0, 5.0, 8.0, 10.0] + + +def can_reach_gap_with_side_wall( + gap_blocks: int, + dy: float, + wall_offset: int, + sprint: bool = True, + momentum_ticks: int = 12, +) -> tuple[bool, Optional[float], float]: + """Check gap reachability with a side wall parallel to the jump direction. + + wall_offset=0 means the wall is flush with the platform edge (wall at z=1.0 + for a 1-wide platform centered at z=0.5). wall_offset=1 means one air block + between the platform edge and the wall face. + + Sweeps yaw angles from 0 to 10 degrees toward the wall to find the + worst-case trajectory. Uses the most pessimistic result: if any realistic + yaw angle causes a failure, the case is marked unreachable or gets a + reduced margin. This models the real-world constraint where MCC's + pathfinder can't guarantee perfect yaw alignment. + """ + if dy > 1.252: + return False, None, 0.0 + + wall_z = 1.0 + wall_offset + start_z = 0.5 + + clearance = wall_z - (start_z + HALF_WIDTH) + if clearance < 0: + return False, None, 0.0 + + needed_x = 0.5 + gap_blocks - HALF_WIDTH + landing_platform_start = 0.5 + gap_blocks + if gap_blocks == 0 and dy > 0: + landing_platform_start = 0.5 + + worst_ok = True + worst_landing_x: Optional[float] = None + worst_margin: Optional[float] = None + + for yaw in SIDE_WALL_YAW_SWEEP: + result = get_landing( + sprint=sprint, + target_y=dy, + landing_x_start=landing_platform_start, + momentum_ticks=momentum_ticks, + landing_width=TARGET_BLOCK_WIDTH, + yaw_degrees=yaw, + wall_z=wall_z, + start_z=start_z, + ) + + if result is None: + return False, worst_landing_x, needed_x + + landing_x, landing_y = result + if abs(landing_y - dy) > 0.01: + return False, landing_x, needed_x + + if gap_blocks > 0 and landing_x < needed_x: + return False, landing_x, needed_x + + margin = landing_x - needed_x + if worst_margin is None or margin < worst_margin: + worst_margin = margin + worst_landing_x = landing_x + + return True, worst_landing_x, needed_x diff --git a/tools/pathing_theory/renderers.py b/tools/pathing_theory/renderers.py new file mode 100644 index 0000000000..e2808d9d1f --- /dev/null +++ b/tools/pathing_theory/renderers.py @@ -0,0 +1,91 @@ +import csv +import json +from dataclasses import asdict +from pathlib import Path + +from tools.pathing_theory.capabilities import format_momentum_capability_lines +from tools.pathing_theory.models import ( + CanonicalLiveCase, + MomentumCapabilityBand, + TheoryCase, +) + + +def write_theory_artifacts( + cases: list[TheoryCase], + canonical_cases: list[CanonicalLiveCase], + capability_bands: list[MomentumCapabilityBand], + output_dir: Path, +) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + + json_path = output_dir / "theory-matrix.json" + csv_path = output_dir / "theory-matrix.csv" + md_path = output_dir / "theory-matrix.md" + canonical_path = output_dir / "canonical-live-cases.json" + capability_path = output_dir / "momentum-capabilities.json" + capability_md_path = output_dir / "momentum-capabilities.md" + + json_path.write_text( + json.dumps([asdict(case) for case in cases], indent=2) + "\n", + encoding="utf-8", + ) + canonical_path.write_text( + json.dumps([asdict(case) for case in canonical_cases], indent=2) + "\n", + encoding="utf-8", + ) + capability_path.write_text( + json.dumps([asdict(band) for band in capability_bands], indent=2) + "\n", + encoding="utf-8", + ) + + with csv_path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=list(asdict(cases[0]).keys())) + writer.writeheader() + for case in cases: + writer.writerow(asdict(case)) + + lines = [ + "# Theory Matrix", + "", + "## Canonical live coverage", + "", + "This file is generated from `tools/pathing_theory/simulator.py` and is the first-wave authority", + "for theory-aligned linear, neo, headhitter, and sidewall live suites.", + "", + "| family | subfamily | movement_mode | case_id | expected_reachable | margin |", + "| --- | --- | --- | --- | --- | --- |", + ] + for case in cases: + lines.append( + f"| {case.family} | {case.subfamily} | {case.movement_mode} | " + f"{case.case_id} | {case.expected_reachable} | {case.margin} |" + ) + + md_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + + capability_lines = [ + "# Momentum Capabilities", + "", + "This file compresses the full theory matrix into `mm` breakpoint bands that can", + "be consumed directly by the planner.", + "", + "| family | subfamily | movement_mode | qualifiers | mm_range | reach |", + "| --- | --- | --- | --- | --- | --- |", + ] + for band, line in zip(capability_bands, format_momentum_capability_lines(capability_bands)): + qualifiers: list[str] = [] + if band.delta_y is not None: + qualifiers.append(f"dy={band.delta_y}") + if band.ceiling_height is not None: + qualifiers.append(f"ceil={band.ceiling_height}") + if band.wall_offset is not None: + qualifiers.append(f"wo={band.wall_offset}") + capability_lines.append( + f"| {band.family} | {band.subfamily} | {band.movement_mode} | " + f"{', '.join(qualifiers) if qualifiers else '-'} | " + f"{band.min_mm}..{band.max_mm} | " + f"{line.split(' | ')[-1]} |" + ) + + capability_md_path.write_text("\n".join(capability_lines) + "\n", encoding="utf-8") diff --git a/tools/pathing_theory/report.py b/tools/pathing_theory/report.py new file mode 100644 index 0000000000..b9c50047e4 --- /dev/null +++ b/tools/pathing_theory/report.py @@ -0,0 +1,56 @@ +import json +from pathlib import Path + + +def classify_live_result(expected_result: str, live_result: str) -> str: + if live_result == "invalid_live_case": + return "invalid_live_case" + if expected_result == "pass" and live_result == "pass": + return "expected_pass/live_pass" + if expected_result == "pass" and live_result == "fail": + return "expected_pass/live_fail" + if expected_result == "reject" and live_result == "reject": + return "expected_reject/live_reject" + if expected_result == "reject" and live_result == "pass": + return "expected_reject/live_unexpected_pass" + return "invalid_live_case" + + +def summarize_results(rows: list[dict]) -> dict[str, int]: + summary: dict[str, int] = {} + for row in rows: + key = classify_live_result(row["expected_result"], row["live_result"]) + summary[key] = summary.get(key, 0) + 1 + return summary + + +def build_report(manifest_path: Path, results_path: Path) -> dict: + manifest_rows = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest_by_case = {row["case_id"]: row for row in manifest_rows} + result_rows = [ + json.loads(line) + for line in results_path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + + joined_rows: list[dict] = [] + for row in result_rows: + manifest = manifest_by_case.get(row["case_id"]) + if manifest is None: + joined_rows.append({**row, "classification": "invalid_live_case"}) + continue + joined_rows.append( + { + **manifest, + **row, + "classification": classify_live_result( + manifest["expected_result"], + row["live_result"], + ), + } + ) + + return { + "rows": joined_rows, + "summary": summarize_results(joined_rows), + } diff --git a/tools/pathing_theory/simulator.py b/tools/pathing_theory/simulator.py new file mode 100644 index 0000000000..f559fb3952 --- /dev/null +++ b/tools/pathing_theory/simulator.py @@ -0,0 +1,186 @@ +from tools.pathing_theory.models import TheoryCase +from tools.pathing_theory.primitives import ( + PLAYER_WIDTH, + TARGET_BLOCK_WIDTH, + can_reach_gap, + can_reach_gap_with_side_wall, + get_apex, + get_landing, +) + +MAX_MOMENTUM_TICKS = 12 + + +def _float_token(value: float) -> str: + return f"{value:.1f}".replace("-", "m").replace(".", "p") + + +def build_theory_cases() -> list[TheoryCase]: + cases: list[TheoryCase] = [] + + for sprint, movement_mode in [ + (False, "walk"), + (True, "sprint"), + ]: + for momentum_ticks in range(0, MAX_MOMENTUM_TICKS + 1): + apex_y, _ = get_apex(sprint=sprint, momentum_ticks=momentum_ticks) + for gap in range(0, 8): + for delta_y in [0.0, 1.0, -1.0, -2.0]: + ok, landing_x, needed_x = can_reach_gap( + gap_blocks=gap, + dy=delta_y, + sprint=sprint, + momentum_ticks=momentum_ticks, + ) + subfamily = ( + "flat" + if delta_y == 0.0 + else "ascend" + if delta_y > 0.0 + else "descend" + ) + cases.append( + TheoryCase( + case_id=( + f"linear-{subfamily}-{movement_mode}-mm{momentum_ticks}" + f"-gap{gap}-dy{_float_token(delta_y)}" + ), + family="linear", + subfamily=subfamily, + movement_mode=movement_mode, + momentum_ticks=momentum_ticks, + gap_blocks=gap, + delta_y=delta_y, + ceiling_height=None, + wall_width=None, + wall_offset=None, + expected_reachable=ok, + landing_x=landing_x, + apex_y=apex_y, + margin=None if landing_x is None else landing_x - needed_x, + ) + ) + + for sprint, movement_mode in [ + (False, "walk"), + (True, "sprint"), + ]: + for momentum_ticks in range(0, MAX_MOMENTUM_TICKS + 1): + apex_y, _ = get_apex(sprint=sprint, momentum_ticks=momentum_ticks) + landing = get_landing( + sprint=sprint, + target_y=0.0, + landing_x_start=0.0, + momentum_ticks=momentum_ticks, + ) + for wall_width in [1, 2, 3, 4]: + landing_x = None if landing is None else landing[0] + needed_x = wall_width + PLAYER_WIDTH + margin = None if landing_x is None else landing_x - needed_x + cases.append( + TheoryCase( + case_id=f"neo-neo-{movement_mode}-mm{momentum_ticks}-wall{wall_width}", + family="neo", + subfamily="neo", + movement_mode=movement_mode, + momentum_ticks=momentum_ticks, + gap_blocks=None, + delta_y=0.0, + ceiling_height=None, + wall_width=wall_width, + wall_offset=None, + expected_reachable=margin is not None and margin >= 0.0, + landing_x=landing_x, + apex_y=apex_y, + margin=margin, + ) + ) + + for momentum_ticks in range(0, MAX_MOMENTUM_TICKS + 1): + for ceiling_height in [4.0, 3.0, 2.5, 2.0, 1.8125]: + apex_y, _ = get_apex( + sprint=True, + momentum_ticks=momentum_ticks, + ceiling_y=ceiling_height, + ) + for gap in [1, 2, 3, 4]: + landing = get_landing( + sprint=True, + target_y=0.0, + landing_x_start=0.5 + gap, + momentum_ticks=momentum_ticks, + ceiling_y=ceiling_height, + landing_width=TARGET_BLOCK_WIDTH, + ) + landing_x = None if landing is None else landing[0] + needed_x = 0.5 + gap - (PLAYER_WIDTH / 2.0) + margin = None if landing_x is None else landing_x - needed_x + cases.append( + TheoryCase( + case_id=( + f"ceiling-headhitter-sprint-mm{momentum_ticks}-gap{gap}" + f"-ceil{str(ceiling_height).replace('.', 'p')}" + ), + family="ceiling", + subfamily="headhitter", + movement_mode="sprint", + momentum_ticks=momentum_ticks, + gap_blocks=gap, + delta_y=0.0, + ceiling_height=ceiling_height, + wall_width=None, + wall_offset=None, + expected_reachable=margin is not None and margin >= 0.0, + landing_x=landing_x, + apex_y=apex_y, + margin=margin, + ) + ) + + # --- Side-wall jump family --- + for sprint, movement_mode in [ + (False, "walk"), + (True, "sprint"), + ]: + for momentum_ticks in range(0, MAX_MOMENTUM_TICKS + 1): + apex_y, _ = get_apex(sprint=sprint, momentum_ticks=momentum_ticks) + for wall_offset in [0, 1]: + for gap in range(0, 8): + for delta_y in [0.0, 1.0, -1.0, -2.0]: + ok, landing_x, needed_x = can_reach_gap_with_side_wall( + gap_blocks=gap, + dy=delta_y, + wall_offset=wall_offset, + sprint=sprint, + momentum_ticks=momentum_ticks, + ) + subfamily = ( + "flat" + if delta_y == 0.0 + else "ascend" + if delta_y > 0.0 + else "descend" + ) + cases.append( + TheoryCase( + case_id=( + f"sidewall-{subfamily}-{movement_mode}-mm{momentum_ticks}" + f"-gap{gap}-dy{_float_token(delta_y)}-wo{wall_offset}" + ), + family="sidewall", + subfamily=subfamily, + movement_mode=movement_mode, + momentum_ticks=momentum_ticks, + gap_blocks=gap, + delta_y=delta_y, + ceiling_height=None, + wall_width=None, + wall_offset=wall_offset, + expected_reachable=ok, + landing_x=landing_x, + apex_y=apex_y, + margin=None if landing_x is None else landing_x - needed_x, + ) + ) + + return cases diff --git a/tools/pathing_theory_report.py b/tools/pathing_theory_report.py new file mode 100644 index 0000000000..3b9fd65fe2 --- /dev/null +++ b/tools/pathing_theory_report.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import argparse +import json +from pathlib import Path +import sys + +REPO_ROOT = Path(__file__).resolve().parent.parent +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + +from tools.pathing_theory.report import build_report + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Join theory-aligned live results back to canonical cases.", + ) + parser.add_argument("--manifest", required=True) + parser.add_argument("--results", required=True) + parser.add_argument("--json-out", required=True) + args = parser.parse_args() + + report = build_report(Path(args.manifest), Path(args.results)) + Path(args.json_out).write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(f"Wrote report to {args.json_out}") + + +if __name__ == "__main__": + main() diff --git a/tools/sim_jump_reach.py b/tools/sim_jump_reach.py new file mode 100644 index 0000000000..2ceee2a6d1 --- /dev/null +++ b/tools/sim_jump_reach.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" +Minecraft Jump Reachability Simulator (Java Edition 1.14+) + +Simulates vanilla player physics tick-by-tick to determine which jump +destinations are reachable. Covers: + - Linear jumps: flat, ascending (+N), descending (-N) + - Sprint jumps vs walk jumps + - Neo jumps (wall jumps): 1-block and 2-block wide walls + - Headhitter (2bc ceiling) jumps + +All physics constants match vanilla 1.21.x / MCC's PhysicsConsts.cs. + +Usage: + python3 sim_jump_reach.py [--verbose] [--csv output.csv] +""" + +import argparse +import csv +from pathlib import Path +import sys + +REPO_ROOT = Path(__file__).resolve().parent.parent +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) +from tools.pathing_theory.capabilities import ( + build_momentum_capability_bands, + format_momentum_capability_lines, +) +from tools.pathing_theory.canonical import build_canonical_live_cases +from tools.pathing_theory.primitives import ( + PLAYER_WIDTH, + can_reach_gap, + can_reach_gap_with_side_wall, + get_apex, + get_landing, + simulate_jump, +) +from tools.pathing_theory.renderers import write_theory_artifacts +from tools.pathing_theory.simulator import build_theory_cases + + +# ============================================================ +# Main analysis +# ============================================================ + +def analyze_all(verbose: bool = False) -> list[dict]: + results = [] + + print("=" * 78) + print(" Minecraft Jump Reachability Analysis (Java 1.14+)") + print(" Physics: vanilla 1.21.x constants from PhysicsConsts.cs") + print("=" * 78) + + # --- Part 1: Apex --- + print("\n[1] Jump Apex (Maximum Height)") + print(f" {'Mode':<8} {'Momentum':>8} {'Apex Y':>10} {'X at Apex':>12}") + print(f" {'----':<8} {'--------':>8} {'------':>10} {'---------':>12}") + for sprint in [False, True]: + for mm in [0, 6, 12, 20]: + ay, ax = get_apex(sprint=sprint, momentum_ticks=mm) + label = "Sprint" if sprint else "Walk" + print(f" {label:<8} {mm:>6}t {ay:>10.4f} {ax:>12.4f}") + results.append({'type': 'apex', 'sprint': sprint, + 'momentum': mm, 'apex_y': ay, 'x_at_apex': ax}) + + # --- Part 2: Landing distances (flat and descending) --- + print(f"\n[2] Landing Distance (sprint, 12t momentum)") + print(f" {'dy':>6} {'Landing X':>12}") + print(f" {'--':>6} {'---------':>12}") + for dy in [0.0, -1.0, -2.0, -3.0, -5.0, -10.0]: + r = get_landing(sprint=True, target_y=dy, + landing_x_start=0.0 if dy <= 0 else 0.5, + momentum_ticks=12) + sign = "+" if dy > 0 else " " if dy == 0 else "" + if r: + print(f" {sign}{dy:>5.1f} {r[0]:>12.4f}m") + else: + print(f" {sign}{dy:>5.1f} {'N/A':>12}") + + # --- Part 3: Full feasibility matrix --- + print(f"\n[3] Gap Feasibility Matrix (Sprint, 12t momentum)") + print(f" Player width={PLAYER_WIDTH}m, max jump height=~1.252b") + print() + + dy_values = [1.0, 0.5, 0.0, -1.0, -2.0, -3.0, -5.0] + header = f" {'Gap':>4}" + for dy in dy_values: + sign = "+" if dy > 0 else "" + header += f" {sign}{dy:>5.1f}" + print(header) + print(f" {'----':>4}" + " ------" * len(dy_values)) + + for gap in range(0, 7): + row = f" {gap:>4}" + for dy in dy_values: + ok, lx, needed = can_reach_gap(gap, dy, sprint=True, momentum_ticks=12) + if ok: + row += f" {'YES':>6}" + elif lx is None: + row += f" {'N/A':>6}" + else: + row += f" {'no':>6}" + print(row) + + # Walk version + print(f"\n Walk jump (no sprint), 12t momentum:") + header = f" {'Gap':>4}" + for dy in dy_values: + sign = "+" if dy > 0 else "" + header += f" {sign}{dy:>5.1f}" + print(header) + print(f" {'----':>4}" + " ------" * len(dy_values)) + + for gap in range(0, 6): + row = f" {gap:>4}" + for dy in dy_values: + ok, lx, needed = can_reach_gap(gap, dy, sprint=False, momentum_ticks=12) + if ok: + row += f" {'YES':>6}" + elif lx is None: + row += f" {'N/A':>6}" + else: + row += f" {'no':>6}" + print(row) + + # Standing jump (0 momentum) + print(f"\n Standing sprint jump (0t momentum):") + header = f" {'Gap':>4}" + for dy in dy_values: + sign = "+" if dy > 0 else "" + header += f" {sign}{dy:>5.1f}" + print(header) + print(f" {'----':>4}" + " ------" * len(dy_values)) + + for gap in range(0, 5): + row = f" {gap:>4}" + for dy in dy_values: + ok, lx, needed = can_reach_gap(gap, dy, sprint=True, momentum_ticks=0) + if ok: + row += f" {'YES':>6}" + elif lx is None: + row += f" {'N/A':>6}" + else: + row += f" {'no':>6}" + print(row) + + # --- Part 4: Neo analysis --- + print(f"\n[4] Neo Jump Analysis (flat, 12t momentum)") + print(f" Wall extends perpendicular to movement.") + print(f" Player must travel wall_length + {PLAYER_WIDTH}m to clear wall end.\n") + print(f" {'Wall':>5} {'Mode':<8} {'LandingX':>10} {'Needed':>10} {'Margin':>10} {'OK':>6}") + print(f" {'----':>5} {'----':<8} {'--------':>10} {'------':>10} {'------':>10} {'--':>6}") + + for wall_len in [1, 2, 3, 4]: + for sprint in [True, False]: + r = get_landing(sprint=sprint, target_y=0.0, + landing_x_start=0.0, momentum_ticks=12) + label = "Sprint" if sprint else "Walk" + if r is None: + print(f" {wall_len:>5} {label:<8} {'N/A':>10}") + continue + lx = r[0] + needed = wall_len + PLAYER_WIDTH + margin = lx - needed + ok = "YES" if margin >= 0 else "no" + print(f" {wall_len:>5} {label:<8} {lx:>10.4f} {needed:>10.4f} " + f"{margin:>+10.4f} {ok:>6}") + results.append({'type': 'neo', 'wall': wall_len, 'sprint': sprint, + 'reach': lx, 'needed': needed, 'margin': margin, + 'ok': margin >= 0}) + + # --- Part 5: Ceiling --- + print(f"\n[5] Ceiling-Constrained Jumps (Sprint, 12t mm, flat)") + base_r = get_landing(sprint=True, target_y=0.0, momentum_ticks=12) + base_lx = base_r[0] if base_r else 0 + print(f" {'Ceiling':>8} {'LandingX':>12} {'Delta':>10}") + for ceil in [4.0, 3.0, 2.5, 2.0, 1.8125]: + r = get_landing(sprint=True, target_y=0.0, momentum_ticks=12, + ceiling_y=ceil) + if r: + diff = r[0] - base_lx + print(f" {ceil:>7.4f}b {r[0]:>11.4f}m {diff:>+10.4f}") + else: + print(f" {ceil:>7.4f}b {'N/A':>12}") + + # --- Part 6: Side-wall jumps --- + print(f"\n[6] Side-Wall Jump Feasibility (Sprint, 12t momentum)") + print(f" Wall parallel to jump direction, flush with platform edge (wo=0)") + print(f" Yaw sweep 0-10 deg, worst-case used.") + print() + + dy_values_sw = [1.0, 0.0, -1.0, -2.0] + header_sw = f" {'Gap':>4}" + for dy in dy_values_sw: + sign = "+" if dy > 0 else "" + header_sw += f" {sign}{dy:>5.1f}(L) {sign}{dy:>5.1f}(W)" + print(header_sw) + print(f" {'----':>4}" + " ---------- ----------" * len(dy_values_sw)) + + for gap in range(0, 7): + row = f" {gap:>4}" + for dy in dy_values_sw: + ok_lin, _, _ = can_reach_gap(gap, dy, sprint=True, momentum_ticks=12) + ok_sw, _, _ = can_reach_gap_with_side_wall( + gap, dy, wall_offset=0, sprint=True, momentum_ticks=12, + ) + lin_str = "YES" if ok_lin else "no" + sw_str = "YES" if ok_sw else "no" + marker = " " if ok_lin == ok_sw else "*" + row += f" {lin_str:>6} {sw_str:>6}{marker}" + print(row) + + print() + print(" L=linear (no wall), W=wall (wo=0), *=reachability differs") + + # --- Part 7: Verbose --- + if verbose: + for label, sp in [("Sprint", True), ("Walk", False)]: + print(f"\n[V] {label} Jump Trajectory (12t momentum, flat)") + print(f" {'Tick':>4} {'X':>10} {'Y':>10} {'Z':>10} {'VX':>10} {'VY':>10} {'VZ':>10} {'Gnd':>5}") + traj = simulate_jump(sprint=sp, momentum_ticks=12, landing_y=0.0) + for s in traj: + g = "G" if s.on_ground else "" + print(f" {s.tick:>4} {s.x:>10.4f} {s.y:>10.4f} {s.z:>10.4f} " + f"{s.vx:>10.6f} {s.vy:>10.6f} {s.vz:>10.6f} {g:>5}") + + print(f"\n[V] Sprint flat with side wall (12t mm, yaw=10, wo=0)") + print(f" {'Tick':>4} {'X':>10} {'Y':>10} {'Z':>10} {'VX':>10} {'VY':>10} {'VZ':>10} {'Gnd':>5}") + traj = simulate_jump(sprint=True, momentum_ticks=12, landing_y=0.0, + landing_x_start=4.5, landing_width=1.0, + yaw_degrees=10.0, wall_z=1.0, start_z=0.5) + for s in traj: + g = "G" if s.on_ground else "" + print(f" {s.tick:>4} {s.x:>10.4f} {s.y:>10.4f} {s.z:>10.4f} " + f"{s.vx:>10.6f} {s.vy:>10.6f} {s.vz:>10.6f} {g:>5}") + + return results + + +def list_momentum_capabilities() -> None: + cases = build_theory_cases() + bands = build_momentum_capability_bands(cases) + for line in format_momentum_capability_lines(bands): + print(line) + + +def main(): + parser = argparse.ArgumentParser( + description="Minecraft jump reachability simulator (Java 1.14+)") + parser.add_argument("--verbose", "-v", action="store_true", + help="Print per-tick trajectory data") + parser.add_argument("--csv", type=str, default=None, + help="Export results to CSV file") + parser.add_argument("--write-artifacts", type=str, default=None, + help="Write tracked theory artifacts to a directory") + parser.add_argument("--list-capabilities", action="store_true", + help="List compressed mm breakpoint capabilities") + args = parser.parse_args() + + if args.write_artifacts: + cases = build_theory_cases() + canonical_cases = build_canonical_live_cases(cases) + capability_bands = build_momentum_capability_bands(cases) + write_theory_artifacts( + cases, + canonical_cases, + capability_bands, + Path(args.write_artifacts), + ) + print(f"Wrote theory artifacts to {args.write_artifacts}") + return + + if args.list_capabilities: + list_momentum_capabilities() + return + + results = analyze_all(verbose=args.verbose) + + if args.csv and results: + keys = set() + for r in results: + keys.update(r.keys()) + with open(args.csv, "w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=sorted(keys)) + writer.writeheader() + writer.writerows(results) + print(f"\nResults exported to {args.csv}") + + +if __name__ == "__main__": + main() diff --git a/tools/test-mcc-env.sh b/tools/test-mcc-env.sh index 38c50c848c..ba7cf7e05c 100755 --- a/tools/test-mcc-env.sh +++ b/tools/test-mcc-env.sh @@ -82,6 +82,26 @@ mkdir -p "$build_root/probe" mcc-build-clean [[ ! -e "$build_root/probe" ]] +runtime_dir="$(_mcc_runtime_output_dir)" +mkdir -p "$runtime_dir" +printf '#!/usr/bin/env bash\n' > "$runtime_dir/MinecraftClient" +chmod +x "$runtime_dir/MinecraftClient" +printf '' > "$runtime_dir/MinecraftClient.dll" +assert_eq "$runtime_dir/MinecraftClient" "$(_mcc_runtime_app_path)" "runtime apphost preferred" + +rm -f "$runtime_dir/MinecraftClient" +assert_eq "$runtime_dir/MinecraftClient.dll" "$(_mcc_runtime_app_path)" "runtime dll fallback" + +rm -f "$runtime_dir/MinecraftClient.dll" +set +e +_mcc_runtime_app_path >/dev/null 2>&1 +status=$? +set -e +if [[ $status -eq 0 ]]; then + echo "FAIL: runtime app path resolved without runtime artifacts" >&2 + exit 1 +fi + session="wrapper-smoke" input_file="$(_mcc_session_input_file "$session")" rm -rf "$(_mcc_session_root "$session")" diff --git a/tools/test-parkour.py b/tools/test-parkour.py new file mode 100644 index 0000000000..a7e0ecd1fe --- /dev/null +++ b/tools/test-parkour.py @@ -0,0 +1,1941 @@ +#!/usr/bin/env python3 +"""Full-coverage parkour test suite for MCC pathfinding. + +Reads momentum-capabilities.json to derive a test matrix, builds multi-segment +jump courses via RCON, and verifies MCC can navigate (or correctly reject) each +one. Stops testing larger gaps once the first failure is seen per group. + +Usage: + source tools/mcc-env.sh + python3 tools/test-parkour.py [OPTIONS] + +Options: + --list-cases Print test matrix and exit + --dry-run Build courses only, do not navigate + --filter PATTERN Hierarchical filter (see examples below) + --username NAME MCC username base (default: MCCBot) + --rcon-port PORT RCON port (default: 25575) + --rcon-password PASS RCON password (default: test123) + --wait SECONDS Seconds to wait per navigation (default: 15) + --results PATH Write JSONL results to PATH + --parallel N Run N MCC instances in parallel (default: 6) + --version VER MC server version for auto-launching MCC + (default: 1.21.11-Vanilla) + --server-port PORT MC server port for auto-launched clients + (default: 25565) + +Filter examples: + --filter linear All linear tests + --filter linear/flat Only linear flat (dy=0) + --filter linear/ascend Only linear ascend (dy>0) + --filter linear/descend/dy-1 Linear descend, dy=-1 only + --filter neo All neo tests + --filter ceiling All ceiling tests + --filter ceiling/headhitter/ceil2.5 Ceiling with height 2.5 + --filter linear-flat-gap4 Exact case_id match + --filter sidewall/flat/wo0 Sidewall flat with wall_offset=0 + + Multiple filters: --filter linear,sidewall +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import queue +import re +import socket +import struct +import subprocess +import sys +import threading +import time +import uuid +from dataclasses import dataclass, field +from pathlib import Path +from typing import Callable, Optional + +REPO_ROOT = Path(__file__).resolve().parent.parent +CAPABILITIES_PATH = REPO_ROOT / "tools" / "pathing_data" / "momentum-capabilities.json" + +SEGMENTS = 3 +CLEAR_PADDING = 8 # air padding on each side of a course in Z +Y_CLEAR_HALF = 30 # clear 30 blocks above and below floor in Y +LINEAR_RUNWAY = 4 +SIDEWALL_RUNWAY = 2 + +CEILING_HEIGHTS_TO_TEST = [2, 3, 4] + +SKIP_FAMILIES: set[str] = set() + +NEO_MAX_WALL_WIDTH = 3 # theoretical max passable width; 4 is the first reject +POLL_INTERVAL_SECONDS = 0.25 +TURN_STALL_MIN_SAMPLES = 4 +TURN_STALL_WINDOW_MAX_TRAVEL = 0.35 +TURN_STALL_MIN_CUMULATIVE_YAW = 180.0 +TURN_STALL_MIN_PER_STEP_YAW = 35.0 + +ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m") +ENTITY_POS_RE = re.compile(r"\[\s*(-?[\d.]+)d,\s*(-?[\d.]+)d,\s*(-?[\d.]+)d\]") +ENTITY_ROT_RE = re.compile(r"\[\s*(-?[\d.]+)f,\s*(-?[\d.]+)f\]") +DEBUG_STATE_LOCATION_RE = re.compile( + r"Location\s*:?\s*(?P-?[\d.]+),\s*(?P-?[\d.]+),\s*(?P-?[\d.]+)" +) +DEBUG_STATE_ON_GROUND_RE = re.compile(r"OnGround\s*:?\s*(?Ptrue|false)", re.IGNORECASE) +ROUTE_COMPLETE_RE = re.compile( + r"\[PathMetric\] routeComplete totalTicks=(?P\d+)(?: replans=(?P\d+))?" +) +SEGMENT_COMPLETE_RE = re.compile( + r"\[PathMetric\] segmentComplete .* x=(?P-?[\d.]+) y=(?P-?[\d.]+) z=(?P-?[\d.]+)" +) +SEGMENT_FAILED_RE = re.compile( + r"\[PathMetric\] segmentFailed .* x=(?P-?[\d.]+) y=(?P-?[\d.]+) z=(?P-?[\d.]+)" +) +REPLAN_START_RE = re.compile(r"\[PathMetric\] replanStart count=(?P\d+)") + +REJECT_PATTERNS = ( + "failed to compute a safe path", + "not a reachable", + "no path", +) + + +# --------------------------------------------------------------------------- +# RCON client +# --------------------------------------------------------------------------- + +class RconClient: + def __init__(self, host: str = "localhost", port: int = 25575, password: str = "test123"): + self._host = host + self._port = port + self._password = password + self._sock: Optional[socket.socket] = None + + def connect(self) -> None: + self._sock = socket.socket() + self._sock.settimeout(5) + self._sock.connect((self._host, self._port)) + self._send(1, 3, self._password) + resp = self._recv() + rid = struct.unpack(" str: + if self._sock is None: + self.connect() + self._send(2, 2, cmd) + resp = self._recv() + return resp[8:-2].decode(errors="replace") + + def close(self) -> None: + if self._sock: + self._sock.close() + self._sock = None + + def _send(self, req_id: int, pkt_type: int, body: str) -> None: + encoded = body.encode() + header = struct.pack(" bytes: + assert self._sock is not None + length_data = b"" + while len(length_data) < 4: + length_data += self._sock.recv(4 - len(length_data)) + length = struct.unpack(" RconClient: + deadline = time.monotonic() + timeout_seconds + last_error: Exception | None = None + + while time.monotonic() < deadline: + client = client_factory(host=host, port=port, password=password) if client_factory else RconClient(host=host, port=port, password=password) + try: + client.connect() + return client + except Exception as exc: + last_error = exc + try: + client.close() + except Exception: + pass + time.sleep(poll_interval) + + if last_error is not None: + raise RuntimeError(f"RCON unavailable on {host}:{port}") from last_error + raise RuntimeError(f"RCON unavailable on {host}:{port}") + + +# --------------------------------------------------------------------------- +# MCC command interface +# --------------------------------------------------------------------------- + +class MccClient: + def __init__(self, session: str): + self.session = session + session_root = Path(os.environ.get("TMPDIR", "/tmp")) / "mcc-debug" / session + self.input_file = session_root / "mcc_input.txt" + self.log_file = session_root / "mcc-debug.log" + + def send(self, command: str) -> None: + self.input_file.parent.mkdir(parents=True, exist_ok=True) + with self.input_file.open("a") as f: + f.write(command + "\n") + + def clear_log(self) -> None: + with self.log_file.open("w") as f: + f.truncate(0) + + def log_length(self) -> int: + if not self.log_file.exists(): + return 0 + return self.log_file.stat().st_size + + def read_log(self) -> str: + if not self.log_file.exists(): + return "" + return self.log_file.read_text(errors="replace").replace("\x00", "") + + def read_log_from(self, offset: int) -> str: + if not self.log_file.exists(): + return "" + with self.log_file.open("rb") as f: + f.seek(offset) + return f.read().decode(errors="replace").replace("\x00", "") + + def strip_ansi(self, text: str) -> str: + return ANSI_ESCAPE_RE.sub("", text) + + +# --------------------------------------------------------------------------- +# Test matrix generation +# --------------------------------------------------------------------------- + +@dataclass +class TestCase: + case_id: str + family: str + subfamily: str + gap_or_wall: int + delta_y: float + ceiling_height: Optional[float] + wall_offset: Optional[int] + expected: str # "pass" or "reject" + + def group_key(self) -> tuple: + """Key for stop-at-first-failure grouping.""" + return (self.family, self.subfamily, self.delta_y, + self.ceiling_height, self.wall_offset) + + def label(self) -> str: + return self.case_id + + +def load_capabilities(path: Path) -> list[dict]: + with path.open() as f: + return json.load(f) + + +def derive_test_matrix(caps: list[dict]) -> list[TestCase]: + """Derive the test matrix from momentum capabilities. + + For each unique (family, subfamily, qualifiers) combination, finds the + global max_reach across all movement modes and momentum ranges, then + generates gap values from 0 to max_reach+1. The max_reach+1 case is the + sole expected-reject case per group. + """ + grouped: dict[tuple, int] = {} + + for band in caps: + family = band["family"] + subfamily = band["subfamily"] + metric = band["capability_metric"] + reach = band["max_reach"] + if reach is None: + continue + + dy = band.get("delta_y") + ceil = band.get("ceiling_height") + wo = band.get("wall_offset") + + if family == "ceiling": + if ceil not in CEILING_HEIGHTS_TO_TEST: + continue + + if family in SKIP_FAMILIES: + continue + + key = (family, subfamily, dy, ceil, wo, metric) + if key not in grouped or reach > grouped[key]: + grouped[key] = reach + + cases: list[TestCase] = [] + for key, max_reach in sorted(grouped.items()): + family, subfamily, dy, ceil, wo, metric = key + + if family == "neo": + max_reach = min(max_reach, NEO_MAX_WALL_WIDTH) + + for value in range(0, max_reach + 2): + if family == "neo" and value == 0: + continue + + if family == "ceiling" and value == 0: + continue + + if family == "sidewall": + wt = (wo or 0) + 1 + if value <= wt: + continue + + expected = "pass" if value <= max_reach else "reject" + + qualifier_parts = [] + if dy is not None and dy != 0.0: + qualifier_parts.append(f"dy{dy:+.0f}") + if ceil is not None: + qualifier_parts.append(f"ceil{ceil}") + if wo is not None: + qualifier_parts.append(f"wo{wo}") + + qualifier_str = "-".join(qualifier_parts) if qualifier_parts else "" + value_label = f"gap{value}" if metric == "gap_blocks" else f"wall{value}" + parts = [family, subfamily, value_label] + if qualifier_str: + parts.append(qualifier_str) + case_id = "-".join(parts) + + cases.append(TestCase( + case_id=case_id, + family=family, + subfamily=subfamily, + gap_or_wall=value, + delta_y=dy if dy is not None else 0.0, + ceiling_height=ceil, + wall_offset=wo, + expected=expected, + )) + + return cases + + +# --------------------------------------------------------------------------- +# Filtering +# --------------------------------------------------------------------------- + +def matches_filter(case: TestCase, pattern: str) -> bool: + """Check if a test case matches a hierarchical filter pattern. + + Supports: + - Exact case_id match: "linear-flat-gap4" + - Family: "linear" + - Family/subfamily: "linear/flat" + - With qualifiers: "linear/descend/dy-1", "sidewall/flat/wo0", + "ceiling/headhitter/ceil2.5" + """ + if case.case_id == pattern: + return True + + parts = pattern.split("/") + + if parts[0] != case.family: + return False + if len(parts) == 1: + return True + + if parts[1] != case.subfamily: + return False + if len(parts) == 2: + return True + + for qualifier in parts[2:]: + q_lower = qualifier.lower() + matched = False + + if q_lower.startswith("dy"): + try: + target_dy = float(q_lower[2:]) + if case.delta_y == target_dy: + matched = True + except ValueError: + pass + elif q_lower.startswith("ceil"): + try: + target_ceil = float(q_lower[4:]) + if case.ceiling_height == target_ceil: + matched = True + except ValueError: + pass + elif q_lower.startswith("wo"): + try: + target_wo = int(q_lower[2:]) + if case.wall_offset == target_wo: + matched = True + except ValueError: + pass + elif q_lower.startswith("gap"): + try: + target_gap = int(q_lower[3:]) + if case.gap_or_wall == target_gap: + matched = True + except ValueError: + pass + elif q_lower.startswith("wall"): + try: + target_wall = int(q_lower[4:]) + if case.gap_or_wall == target_wall: + matched = True + except ValueError: + pass + + if not matched: + return False + + return True + + +def apply_filters(cases: list[TestCase], filter_str: str) -> list[TestCase]: + """Apply comma-separated filter patterns to the case list.""" + patterns = [p.strip() for p in filter_str.split(",")] + return [c for c in cases if any(matches_filter(c, p) for p in patterns)] + + +# --------------------------------------------------------------------------- +# World building +# --------------------------------------------------------------------------- + +@dataclass +class CourseLayout: + start_x: int + start_y: int + start_z: int + end_x: int + end_y: int + end_z: int + clear_min: tuple[int, int, int] + clear_max: tuple[int, int, int] + + +class WorldBuilder: + Z_START = 100 + + def __init__(self, rcon: RconClient, base_x: int = 100, base_y: int = 80): + self.rcon = rcon + self.base_x = base_x + self.base_y = base_y + self._z_cursor = self.Z_START + + def allocate_z(self, width: int = 1) -> int: + """Allocate a Z band for a course. + + Layout: [CLEAR_PADDING] [course width] ... + Adjacent courses share the padding between them: the trailing + padding of course N is the leading padding of course N+1. + """ + z = self._z_cursor + CLEAR_PADDING + self._z_cursor = z + width + return z + + def reset_z(self) -> None: + self._z_cursor = self.Z_START + + def compute_z_extent(self, cases: list) -> int: + """Dry-run Z allocation to find the final Z cursor value.""" + saved = self._z_cursor + for case in cases: + width = self._course_z_width(case) + self.allocate_z(width) + end = self._z_cursor + CLEAR_PADDING + self._z_cursor = saved + return end + + def _course_z_width(self, case: TestCase) -> int: + if case.family == "sidewall": + gap = case.gap_or_wall + return gap * SEGMENTS + 5 + elif case.family == "neo": + return 5 + else: + return 1 + + def forceload_region(self, z_end: int) -> None: + """Force-load all chunks covering the test region.""" + x_min = self.base_x - 20 + x_max = self.base_x + 40 + z_min = self.Z_START - CLEAR_PADDING + z_max = z_end + cx_min = x_min >> 4 + cx_max = x_max >> 4 + cz_min = z_min >> 4 + cz_max = z_max >> 4 + for cx in range(cx_min, cx_max + 1): + self.rcon.command( + f"forceload add {cx * 16} {cz_min * 16} {cx * 16 + 15} {cz_max * 16 + 15}" + ) + print(f" Force-loaded chunks: X=[{cx_min},{cx_max}] Z=[{cz_min},{cz_max}]") + + def forceload_remove(self) -> None: + self.rcon.command("forceload remove all") + + def clear_entire_region(self, z_end: int) -> None: + """Clear the full test region from Z_START to z_end.""" + x_min = self.base_x - 20 + x_max = self.base_x + 40 + y_min = self.base_y - 1 - Y_CLEAR_HALF + y_max = self.base_y - 1 + Y_CLEAR_HALF + z_min = self.Z_START - CLEAR_PADDING + z_max = z_end + print(f" Clearing region: X=[{x_min},{x_max}] Y=[{y_min},{y_max}] Z=[{z_min},{z_max}]") + self._fill_volume(x_min, y_min, z_min, x_max, y_max, z_max, "air") + + def _fill_volume(self, x1: int, y1: int, z1: int, + x2: int, y2: int, z2: int, block: str) -> None: + """Fill a volume respecting MC's 32768-block-per-call limit.""" + dx = x2 - x1 + 1 + dy = y2 - y1 + 1 + max_z_per_call = max(1, 32768 // (dx * dy)) + for cz in range(z1, z2 + 1, max_z_per_call): + ez = min(cz + max_z_per_call - 1, z2) + self.rcon.command(f"fill {x1} {y1} {cz} {x2} {y2} {ez} {block}") + + def set_block(self, x: int, y: int, z: int, block: str = "stone") -> None: + self.rcon.command(f"setblock {x} {y} {z} {block}") + + def fill_blocks(self, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, + block: str = "stone") -> None: + self.rcon.command(f"fill {x1} {y1} {z1} {x2} {y2} {z2} {block}") + + def _make_layout(self, bx: int, by: int, bz: int, + end_x: int, end_y: int, end_z: int) -> CourseLayout: + return CourseLayout( + start_x=bx, start_y=by, start_z=bz, + end_x=end_x, end_y=end_y, end_z=end_z, + clear_min=(0, 0, 0), clear_max=(0, 0, 0), + ) + + def build_linear_route(self, case: TestCase) -> CourseLayout: + gap = case.gap_or_wall + dy = case.delta_y + bx = self.base_x + by = self.base_y + bz = self.allocate_z() + floor_y = by - 1 + + self.fill_blocks(bx, floor_y, bz, bx + LINEAR_RUNWAY - 1, floor_y, bz) + + last_x = bx + LINEAR_RUNWAY - 1 + last_y = floor_y + + for seg in range(SEGMENTS): + plat_x = last_x + gap + 1 + plat_y = last_y + int(dy) + self.set_block(plat_x, plat_y, bz) + last_x = plat_x + last_y = plat_y + + return self._make_layout(bx, by, bz, last_x, last_y + 1, bz) + + def build_neo_route(self, case: TestCase) -> CourseLayout: + """Neo jump: wall blocks the +X path, player detours via +Z. + + Top view of one segment (wall_width=3): + + Z ^ + | + bz+1| ...(detour: player jumps to Z+1 in air, past wall, back)... + | + bz | [Runway] [W][W][W] [Land][Land] [W][W][W] ... + | X=0..3 X=4..6 X=7..8 X=9..11 + +----------------------------------------------> X + + The wall is wall_width blocks in X, 1 block in Z (at Z=bz), 8 tall. + 2-block landing gap between consecutive walls. + """ + wall_width = case.gap_or_wall + bx = self.base_x + by = self.base_y + bz = self.allocate_z(width=5) + floor_y = by - 1 + + cur_x = bx + + self.fill_blocks(bx, floor_y, bz, bx + LINEAR_RUNWAY - 1, floor_y, bz) + cur_x += LINEAR_RUNWAY - 1 + + for seg in range(SEGMENTS): + wall_start_x = cur_x + 1 + wall_end_x = wall_start_x + wall_width - 1 + if wall_width > 0: + self.fill_blocks(wall_start_x, floor_y, bz, + wall_end_x, floor_y + 7, bz) + + land_x1 = wall_end_x + 1 + land_x2 = land_x1 + 1 + self.set_block(land_x1, floor_y, bz) + self.set_block(land_x2, floor_y, bz) + + cur_x = land_x2 + + return self._make_layout(bx, by, bz, cur_x, by, bz) + + def build_sidewall_route(self, case: TestCase) -> CourseLayout: + """Around-the-wall jump (绕墙跳). + + Parameters: + gap_or_wall = gap : Z-distance between start and target platforms + wall_offset = wall_thickness (1 or 2): Z-depth of the wall + delta_y: Y-offset of target relative to start (+1, 0, -1, -2) + + Top view (one segment, gap=3, wall_thickness=1, dy=+1): + + Z ^ + | + bz+3| [Target] X=bx-1, Y=floor_y+dy + | + bz+2| (air) + | + bz+1| (air) + | + bz | [Start] X=bx [WALL] X=bx-1, Z=bz..bz+wt-1, 8 tall + +-------> X + + The wall is at X=bx-1 (one block to -X of start), starts at same + Z as the start platform, extends wt blocks in +Z, and is 8 tall. + The target is at X=bx-1, Z=bz+gap, Y=floor_y+dy. + Player must jump from start, around the wall's -X edge, + and land on the target. + """ + gap = case.gap_or_wall + dy = int(case.delta_y) + wt = (case.wall_offset or 0) + 1 # wall_offset=0 -> 1 thick, =1 -> 2 thick + bx = self.base_x + by = self.base_y + floor_y = by - 1 + + total_z = gap * SEGMENTS + 5 + bz = self.allocate_z(width=total_z) + + cur_x = bx + cur_y = floor_y + cur_z = bz + + # Starting platform with 2-block runway in -Z direction + self.fill_blocks(cur_x, cur_y, cur_z - 2, cur_x, cur_y, cur_z) + + for seg in range(SEGMENTS): + wall_x = cur_x - 1 + wall_z_start = cur_z + wall_z_end = cur_z + wt - 1 + wall_y_low = min(cur_y, cur_y + dy) - 1 + wall_y_high = max(cur_y, cur_y + dy) + 7 + + self.fill_blocks(wall_x, wall_y_low, wall_z_start, + wall_x, wall_y_high, wall_z_end) + + land_x = cur_x - 1 + land_y = cur_y + dy + land_z = cur_z + gap + + self.set_block(land_x, land_y, land_z) + + cur_x = land_x + cur_y = land_y + cur_z = land_z + + return self._make_layout(bx, by, bz, cur_x, cur_y + 1, cur_z) + + def build_ceiling_route(self, case: TestCase) -> CourseLayout: + gap = case.gap_or_wall + ceil_height = case.ceiling_height or 4.0 + bx = self.base_x + by = self.base_y + bz = self.allocate_z() + floor_y = by - 1 + + self.fill_blocks(bx, floor_y, bz, bx + LINEAR_RUNWAY - 1, floor_y, bz) + + last_x = bx + LINEAR_RUNWAY - 1 + for seg in range(SEGMENTS): + plat_x = last_x + gap + 1 + self.set_block(plat_x, floor_y, bz) + last_x = plat_x + + ceil_block_y = floor_y + 1 + int(ceil_height) + self.fill_blocks(bx - 1, ceil_block_y, bz - 1, last_x + 1, ceil_block_y, bz + 1) + + return self._make_layout(bx, by, bz, last_x, by, bz) + + def build(self, case: TestCase) -> CourseLayout: + if case.family == "linear": + return self.build_linear_route(case) + if case.family == "neo": + return self.build_neo_route(case) + if case.family == "sidewall": + return self.build_sidewall_route(case) + if case.family == "ceiling": + return self.build_ceiling_route(case) + raise ValueError(f"Unknown family: {case.family}") + + +# --------------------------------------------------------------------------- +# Test execution +# --------------------------------------------------------------------------- + +@dataclass(frozen=True) +class NavigationSample: + x: float + y: float + z: float + yaw: float + + +@dataclass +class LiveMetrics: + route_start_count: int = 0 + route_complete_count: int = 0 + navigation_complete_count: int = 0 + segment_failed_count: int = 0 + replan_count: int = 0 + replan_failed_count: int = 0 + planner_reject_count: int = 0 + generic_fail_count: int = 0 + final_metric_position: tuple[float, float, float] | None = None + total_ticks: int | None = None + turn_stall_count: int = 0 + + +@dataclass +class TestResult: + case: TestCase + outcome: str # "pass", "reject", "fail", "invalid_live_case" + matched_expected: bool + replan_count: int = 0 + turn_stall_count: int = 0 + near_goal: bool | None = None + final_position: tuple[float, float, float] | None = None + total_ticks: int | None = None + log_excerpt: str = "" + session: str | None = None + log_path: str | None = None + event_log_path: str | None = None + duration_ms: int | None = None + error_kind: str | None = None + skip_reason: str | None = None + + +@dataclass(frozen=True) +class DebugStateSnapshot: + location: tuple[float, float, float] + on_ground: bool | None + + +def resolve_session() -> str: + explicit = os.environ.get("SESSION", "") + if explicit: + return explicit + repo = os.environ.get("MCC_REPO_ROOT", "") + if repo: + return Path(repo).name + return Path.cwd().name + + +def make_parallel_run_token() -> str: + return uuid.uuid4().hex[:10] + + +def build_worker_session_name(run_token: str, worker_id: int) -> str: + return f"parkour-{run_token}-{worker_id}" + + +def build_case_session_name(run_token: str, worker_id: int, case_index: int) -> str: + return f"parkour-{run_token}-{worker_id}-c{case_index}" + + +def build_worker_username(base_username: str, worker_id: int) -> str: + return f"{base_username}{worker_id}" + + +def build_case_username(base_username: str, worker_id: int, case_index: int) -> str: + return f"{base_username}{worker_id}c{case_index}" + + +def parse_entity_position(text: str) -> tuple[float, float, float] | None: + match = ENTITY_POS_RE.search(text) + if not match: + return None + return float(match.group(1)), float(match.group(2)), float(match.group(3)) + + +def parse_entity_rotation(text: str) -> tuple[float, float] | None: + match = ENTITY_ROT_RE.search(text) + if not match: + return None + return float(match.group(1)), float(match.group(2)) + + +def parse_debug_state_location(text: str) -> tuple[float, float, float] | None: + snapshot = parse_debug_state_snapshot(text) + if snapshot is None: + return None + return snapshot.location + + +def parse_debug_state_snapshot(text: str) -> DebugStateSnapshot | None: + clean_text = ANSI_ESCAPE_RE.sub("", text).replace("\x00", "") + blocks = [block for block in clean_text.split("=== MCC Debug State ===") if block.strip()] + if not blocks: + return None + + block = blocks[-1] + location_match = DEBUG_STATE_LOCATION_RE.search(block) + if location_match is None: + return None + + on_ground_match = DEBUG_STATE_ON_GROUND_RE.search(block) + return DebugStateSnapshot( + location=( + float(location_match.group("x")), + float(location_match.group("y")), + float(location_match.group("z")), + ), + on_ground=None if on_ground_match is None else on_ground_match.group("value").lower() == "true", + ) + + +def is_near_expected_position( + actual: tuple[float, float, float], + expected: tuple[float, float, float], + horiz_tolerance: float = 0.18, + vert_tolerance: float = 0.05, +) -> bool: + return ( + abs(actual[0] - expected[0]) <= horiz_tolerance + and abs(actual[2] - expected[2]) <= horiz_tolerance + and math.floor(actual[1]) == math.floor(expected[1]) + and abs(actual[1] - expected[1]) <= vert_tolerance + ) + + +def wait_for_local_start_sync( + mcc: MccClient, + expected_position: tuple[float, float, float], + timeout_seconds: float = 8.0, + stable_reads_required: int = 3, +) -> bool: + log_offset = mcc.log_length() + deadline = time.monotonic() + timeout_seconds + stable_reads = 0 + last_snapshot: DebugStateSnapshot | None = None + + while time.monotonic() < deadline: + mcc.send("debug state") + time.sleep(0.25) + + snapshot = parse_debug_state_snapshot(mcc.read_log_from(log_offset)) + if snapshot is None: + time.sleep(0.15) + continue + + if is_near_expected_position(snapshot.location, expected_position) and snapshot.on_ground is True: + if ( + last_snapshot is None + or last_snapshot.on_ground is not True + or is_near_expected_position( + snapshot.location, + last_snapshot.location, + horiz_tolerance=0.08, + vert_tolerance=0.08, + ) + ): + stable_reads += 1 + else: + stable_reads = 1 + + last_snapshot = snapshot + if stable_reads >= stable_reads_required: + return True + else: + stable_reads = 0 + last_snapshot = snapshot + + time.sleep(0.15) + + return False + + +def get_player_sample(rcon: RconClient, username: str) -> NavigationSample | None: + try: + pos = parse_entity_position(rcon.command(f"data get entity {username} Pos")) + rotation = parse_entity_rotation(rcon.command(f"data get entity {username} Rotation")) + except Exception: + return None + + if pos is None or rotation is None: + return None + + return NavigationSample( + x=pos[0], + y=pos[1], + z=pos[2], + yaw=rotation[0], + ) + + +def normalize_yaw_delta(previous_yaw: float, current_yaw: float) -> float: + delta = (current_yaw - previous_yaw + 180.0) % 360.0 - 180.0 + return abs(delta) + + +def horizontal_distance(a: NavigationSample, b: NavigationSample) -> float: + return math.hypot(a.x - b.x, a.z - b.z) + + +def count_turn_stalls(samples: list[NavigationSample]) -> int: + if len(samples) < TURN_STALL_MIN_SAMPLES: + return 0 + + count = 0 + window_start = 0 + while window_start <= len(samples) - TURN_STALL_MIN_SAMPLES: + base = samples[window_start] + cumulative_yaw = 0.0 + large_swings = 0 + matched = False + + for idx in range(window_start + 1, len(samples)): + sample = samples[idx] + if horizontal_distance(base, sample) > TURN_STALL_WINDOW_MAX_TRAVEL: + break + + yaw_delta = normalize_yaw_delta(samples[idx - 1].yaw, sample.yaw) + cumulative_yaw += yaw_delta + if yaw_delta >= TURN_STALL_MIN_PER_STEP_YAW: + large_swings += 1 + + sample_count = idx - window_start + 1 + if ( + sample_count >= TURN_STALL_MIN_SAMPLES + and large_swings >= TURN_STALL_MIN_SAMPLES - 1 + and cumulative_yaw >= TURN_STALL_MIN_CUMULATIVE_YAW + ): + count += 1 + window_start = idx + 1 + matched = True + break + + if not matched: + window_start += 1 + + return count + + +def parse_live_metrics(text: str) -> LiveMetrics: + metrics = LiveMetrics() + clean_text = ANSI_ESCAPE_RE.sub("", text).replace("\x00", "") + + for raw_line in clean_text.splitlines(): + line = raw_line.strip() + lower = line.lower() + + if "[PathMetric] routeStart" in line: + metrics.route_start_count += 1 + + if "[PathMgr] Navigation complete!" in line: + metrics.navigation_complete_count += 1 + + route_match = ROUTE_COMPLETE_RE.search(line) + if route_match: + metrics.route_complete_count += 1 + metrics.total_ticks = int(route_match.group("ticks")) + replans = route_match.group("replans") + if replans is not None: + metrics.replan_count = max(metrics.replan_count, int(replans)) + + replan_match = REPLAN_START_RE.search(line) + if replan_match: + metrics.replan_count = max(metrics.replan_count, int(replan_match.group("count"))) + + segment_complete_match = SEGMENT_COMPLETE_RE.search(line) + if segment_complete_match: + metrics.final_metric_position = ( + float(segment_complete_match.group("x")), + float(segment_complete_match.group("y")), + float(segment_complete_match.group("z")), + ) + + segment_failed_match = SEGMENT_FAILED_RE.search(line) + if segment_failed_match: + metrics.segment_failed_count += 1 + metrics.final_metric_position = ( + float(segment_failed_match.group("x")), + float(segment_failed_match.group("y")), + float(segment_failed_match.group("z")), + ) + + if "replan failed" in lower or "giving up" in lower: + metrics.replan_failed_count += 1 + + planner_reject_line = ( + any(pattern in lower for pattern in REJECT_PATTERNS) + or ("[a*]" in lower and "failed" in lower) + or ("a* result: failed" in lower) + ) + if planner_reject_line: + metrics.planner_reject_count += 1 + + if ( + "failed" in lower + and "replan failed" not in lower + and "failed to compute a safe path" not in lower + and not planner_reject_line + ): + metrics.generic_fail_count += 1 + + return metrics + + +def has_terminal_metrics(metrics: LiveMetrics) -> bool: + return ( + metrics.route_complete_count > 0 + or metrics.segment_failed_count > 0 + or metrics.replan_failed_count > 0 + or (metrics.planner_reject_count > 0 and metrics.route_start_count == 0) + ) + + +def wait_for_rcon_ready( + rcon: RconClient, + timeout_seconds: float = 20.0, + poll_interval: float = 0.5, +) -> bool: + deadline = time.monotonic() + timeout_seconds + while time.monotonic() < deadline: + try: + rcon.command("list") + return True + except Exception: + time.sleep(poll_interval) + return False + + +def is_near_goal( + position: tuple[float, float, float] | None, + layout: CourseLayout, +) -> bool | None: + if position is None: + return None + + px, py, pz = position + goal_x = layout.end_x + 0.5 + goal_y = float(layout.end_y) + goal_z = layout.end_z + 0.5 + return ( + abs(px - goal_x) <= 1.25 + and abs(pz - goal_z) <= 1.25 + and abs(py - goal_y) <= 2.0 + ) + + +def classify_outcome( + metrics: LiveMetrics, + near_goal: bool | None, + error_kind: str | None = None, +) -> str: + if error_kind is not None: + return error_kind + + if metrics.segment_failed_count > 0 or metrics.replan_failed_count > 0 or metrics.generic_fail_count > 0: + return "fail" + + if metrics.route_complete_count > 0 or metrics.navigation_complete_count > 0: + if metrics.replan_count > 0 or metrics.turn_stall_count > 0: + return "fail" + if near_goal is False: + return "fail" + return "pass" + + if metrics.planner_reject_count > 0 and metrics.route_start_count == 0: + return "reject" + + return "invalid_live_case" + + +def run_single_test( + case: TestCase, + layout: CourseLayout, + rcon: RconClient, + mcc: MccClient, + username: str, + wait_seconds: int = 15, +) -> TestResult: + start_time = time.monotonic() + log_offset = mcc.log_length() + + mcc.send(f"send ===== TEST: {case.case_id} (expect: {case.expected}) =====") + time.sleep(0.2) + mcc.send(f"goto {layout.end_x} {layout.end_y} {layout.end_z}") + deadline = time.monotonic() + wait_seconds + settle_deadline: float | None = None + samples: list[NavigationSample] = [] + metrics = LiveMetrics() + + while time.monotonic() < deadline: + sample = get_player_sample(rcon, username) + if sample is not None: + samples.append(sample) + + log = mcc.strip_ansi(mcc.read_log_from(log_offset)) + metrics = parse_live_metrics(log) + if has_terminal_metrics(metrics): + if settle_deadline is None: + settle_deadline = time.monotonic() + 0.5 + elif time.monotonic() >= settle_deadline: + break + + time.sleep(POLL_INTERVAL_SECONDS) + + raw_log = mcc.read_log_from(log_offset) + log = mcc.strip_ansi(raw_log) + all_lines = log.splitlines() + metrics = parse_live_metrics(log) + metrics.turn_stall_count = count_turn_stalls(samples) + + a_star_lines = [l for l in all_lines if "[A*]" in l][:3] + path_mgr_lines = [l for l in all_lines if "[PathMgr]" in l] + + sampled_position = None + if samples: + last_sample = samples[-1] + sampled_position = (last_sample.x, last_sample.y, last_sample.z) + + if metrics.route_complete_count > 0 and metrics.final_metric_position is not None: + final_position = metrics.final_metric_position + else: + final_position = sampled_position or metrics.final_metric_position + + near_goal = is_near_goal(final_position, layout) + outcome = classify_outcome(metrics, near_goal) + + excerpt_lines = [] + if a_star_lines: + excerpt_lines.append(f" A*: {a_star_lines[0]}") + if path_mgr_lines: + excerpt_lines.append(f" Mgr: {path_mgr_lines[-1]}") + excerpt_lines.append( + f" Metrics: routes={metrics.route_complete_count} replans={metrics.replan_count} " + f"turn_stalls={metrics.turn_stall_count} ticks={metrics.total_ticks}" + ) + if final_position is not None: + px, py, pz = final_position + goal_x = layout.end_x + 0.5 + goal_y = float(layout.end_y) + goal_z = layout.end_z + 0.5 + excerpt_lines.append( + f" Pos: ({px:.1f},{py:.1f},{pz:.1f}) goal=({goal_x:.1f},{goal_y:.1f},{goal_z:.1f}) " + f"near={near_goal}" + ) + relevant = [l for l in all_lines if "path" in l.lower() or "move" in l.lower() + or "navigate" in l.lower() or "A*" in l] + if not excerpt_lines and relevant: + excerpt_lines.append(f" Log: {relevant[0]}") + + return TestResult( + case=case, + outcome=outcome, + matched_expected=(outcome == case.expected), + replan_count=metrics.replan_count, + turn_stall_count=metrics.turn_stall_count, + near_goal=near_goal, + final_position=final_position, + total_ticks=metrics.total_ticks, + log_excerpt="\n".join(excerpt_lines), + session=mcc.session, + log_path=str(mcc.log_file), + duration_ms=int((time.monotonic() - start_time) * 1000), + ) + + +# --------------------------------------------------------------------------- +# Stop-at-first-failure logic +# --------------------------------------------------------------------------- + +def should_skip(case: TestCase, failed_groups: set[tuple]) -> bool: + """Skip this case if its group already had a failure at a smaller gap.""" + return case.group_key() in failed_groups + + +def make_skip_result(case: TestCase, reason: str) -> TestResult: + return TestResult( + case=case, + outcome="skipped", + matched_expected=False, + skip_reason=reason, + ) + + +def make_harness_result( + case: TestCase, + error_kind: str, + log_excerpt: str, + session: str | None = None, + log_path: str | None = None, +) -> TestResult: + return TestResult( + case=case, + outcome=error_kind, + matched_expected=False, + log_excerpt=log_excerpt, + session=session, + log_path=log_path, + error_kind=error_kind, + ) + + +def result_to_record(result: TestResult, worker_id: int | None = None) -> dict[str, object]: + record: dict[str, object] = { + "case_id": result.case.case_id, + "family": result.case.family, + "subfamily": result.case.subfamily, + "gap_or_wall": result.case.gap_or_wall, + "expected": result.case.expected, + "outcome": result.outcome, + "matched": result.matched_expected, + "replan_count": result.replan_count, + "turn_stall_count": result.turn_stall_count, + "near_goal": result.near_goal, + "total_ticks": result.total_ticks, + "final_position": list(result.final_position) if result.final_position is not None else None, + "session": result.session, + "log_path": result.log_path, + "event_log_path": result.event_log_path, + "duration_ms": result.duration_ms, + "error_kind": result.error_kind, + "skip_reason": result.skip_reason, + } + if worker_id is not None: + record["worker"] = worker_id + return record + + +def summarize_results(records: list[dict[str, object]]) -> dict[str, object]: + summary: dict[str, object] = { + "total": len(records), + "matched": sum(1 for r in records if bool(r.get("matched"))), + "mismatched": sum(1 for r in records if not bool(r.get("matched"))), + "families": {}, + } + + families: dict[str, dict[str, object]] = {} + for record in records: + family = str(record.get("family", "unknown")) + outcome = str(record.get("outcome", "unknown")) + matched = bool(record.get("matched")) + family_summary = families.setdefault( + family, + { + "total": 0, + "matched": 0, + "mismatches": 0, + "outcomes": {}, + }, + ) + family_summary["total"] = int(family_summary["total"]) + 1 + if matched: + family_summary["matched"] = int(family_summary["matched"]) + 1 + else: + family_summary["mismatches"] = int(family_summary["mismatches"]) + 1 + outcomes = family_summary["outcomes"] + assert isinstance(outcomes, dict) + outcomes[outcome] = int(outcomes.get(outcome, 0)) + 1 + + summary["families"] = families + return summary + + +def create_run_dir(base_dir: Path | None = None) -> Path: + root = base_dir or (Path(os.environ.get("TMPDIR", "/tmp")) / "parkour-runs") + timestamp = time.strftime("%Y%m%d-%H%M%S", time.gmtime()) + run_dir = root / f"{timestamp}-{make_parallel_run_token()}" + run_dir.mkdir(parents=True, exist_ok=False) + return run_dir + + +def write_summary_files(run_dir: Path, summary: dict[str, object]) -> None: + run_dir.mkdir(parents=True, exist_ok=True) + summary_json = run_dir / "summary.json" + summary_md = run_dir / "summary.md" + + summary_json.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + lines = [ + "# Parkour Summary", + "", + f"- Total: {summary.get('total', 0)}", + f"- Matched: {summary.get('matched', 0)}", + f"- Mismatched: {summary.get('mismatched', 0)}", + "", + "## Families", + "", + ] + + families = summary.get("families", {}) + if isinstance(families, dict): + for family, family_summary in sorted(families.items()): + lines.append(f"### {family}") + if isinstance(family_summary, dict): + lines.append(f"- Total: {family_summary.get('total', 0)}") + lines.append(f"- Matched: {family_summary.get('matched', 0)}") + lines.append(f"- Mismatches: {family_summary.get('mismatches', 0)}") + outcomes = family_summary.get("outcomes", {}) + if isinstance(outcomes, dict): + for outcome, count in sorted(outcomes.items()): + lines.append(f"- {outcome}: {count}") + lines.append("") + + summary_md.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8") + + +def append_jsonl_record(paths: list[Path], record: dict[str, object]) -> None: + payload = json.dumps(record) + "\n" + seen: set[Path] = set() + for path in paths: + if path in seen: + continue + seen.add(path) + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding="utf-8") as f: + f.write(payload) + + +# --------------------------------------------------------------------------- +# Parallel worker infrastructure +# --------------------------------------------------------------------------- + +@dataclass +class WorkerContext: + worker_id: int + username: str + session: str + rcon: RconClient + mcc: MccClient + + +_print_lock = threading.Lock() + + +def _tprint(*args: object, **kwargs: object) -> None: + """Thread-safe print.""" + with _print_lock: + print(*args, **kwargs) + + +def _launch_one_mcc(worker_id: int, username: str, session: str, + version: str, server_port: int) -> None: + """Start one MCC instance via mcc-debug. Blocks until mcc-debug returns.""" + mcc_env_sh = REPO_ROOT / "tools" / "mcc-env.sh" + shell_cmd = ( + f"source {mcc_env_sh} && " + f"mcc-debug --session {session} --username {username} " + f"--file-input -v {version} -p {server_port} " + f"--no-build --debug-on" + ) + result = subprocess.run(["bash", "-c", shell_cmd], + capture_output=True, text=True) + if result.returncode != 0: + _tprint(f" [W{worker_id}] ERROR launching MCC:") + if result.stdout.strip(): + _tprint(f" stdout: {result.stdout.strip()}") + if result.stderr.strip(): + _tprint(f" stderr: {result.stderr.strip()}") + raise RuntimeError(f"Failed to launch worker {worker_id}") + + +def _wait_for_join(mcc: MccClient, timeout: float = 30) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + log = mcc.read_log() + if "Server was successfully joined" in log: + return True + time.sleep(1) + return False + + +def launch_worker_context( + worker_id: int, + username: str, + session: str, + version: str, + server_port: int, + rcon_port: int, + rcon_password: str, +) -> WorkerContext | None: + _tprint(f" [W{worker_id}] Launching: session={session} username={username}") + try: + _launch_one_mcc(worker_id, username, session, version, server_port) + except RuntimeError: + _tprint(f" [W{worker_id}] Failed to launch, exiting case.") + return None + + rcon = connect_rcon_with_retry(port=rcon_port, password=rcon_password, timeout_seconds=10.0) + mcc = MccClient(session) + + if _wait_for_join(mcc): + _tprint(f" [W{worker_id}] {username} connected to server.") + else: + _tprint(f" [W{worker_id}] WARNING: {username} join not confirmed " + "(continuing anyway)") + + try: + admin_rcon = RconClient(port=rcon_port, password=rcon_password) + admin_rcon.connect() + admin_rcon.command(f"op {username}") + admin_rcon.close() + except Exception: + pass + + mcc.send("debug on") + time.sleep(2) + + return WorkerContext( + worker_id=worker_id, + username=username, + session=session, + rcon=rcon, + mcc=mcc, + ) + + +def register_worker_context( + ctx: WorkerContext, + workers_registry: list[WorkerContext], + registry_lock: threading.Lock, +) -> None: + with registry_lock: + workers_registry.append(ctx) + + +def reset_worker_state(ctx: WorkerContext, layout: CourseLayout) -> bool: + expected_start_position = ( + layout.start_x + 0.5, + float(layout.start_y), + layout.start_z + 0.5, + ) + + for _attempt in range(2): + ctx.rcon.command(f"gamemode creative {ctx.username}") + ctx.rcon.command( + f"tp {ctx.username} {layout.start_x}.5 {layout.start_y} {layout.start_z}.5" + ) + time.sleep(2) + ctx.rcon.command(f"gamemode survival {ctx.username}") + time.sleep(0.5) + if wait_for_local_start_sync(ctx.mcc, expected_start_position): + return True + time.sleep(0.5) + + return False + + +def worker_loop( + worker_id: int, + base_username: str, + run_token: str, + version: str, + server_port: int, + rcon_port: int, + rcon_password: str, + group_queue: queue.Queue, + all_results: list[TestResult], + results_lock: threading.Lock, + wait_seconds: int, + results_paths: list[Path], + workers_registry: list[WorkerContext], + registry_lock: threading.Lock, + skipped_counter: list[int], +) -> None: + """Run assigned groups while reusing one MCC session per worker.""" + local_results: list[TestResult] = [] + local_skipped = 0 + failed_groups: set[tuple] = set() + worker_session = build_worker_session_name(run_token, worker_id) + worker_username = build_worker_username(base_username, worker_id) + ctx: WorkerContext | None = None + + def write_result(result: TestResult) -> None: + with results_lock: + append_jsonl_record(results_paths, result_to_record(result, worker_id)) + + def ensure_worker() -> WorkerContext | None: + nonlocal ctx + if ctx is not None: + return ctx + + launched = launch_worker_context( + worker_id=worker_id, + username=worker_username, + session=worker_session, + version=version, + server_port=server_port, + rcon_port=rcon_port, + rcon_password=rcon_password, + ) + if launched is not None: + ctx = launched + register_worker_context(ctx, workers_registry, registry_lock) + return ctx + + while True: + try: + _, items = group_queue.get_nowait() + except queue.Empty: + break + + for case, layout in items: + if case.group_key() in failed_groups: + local_skipped += 1 + _tprint(f" [W{worker_id}] {case.case_id} -- SKIPPED") + skipped = make_skip_result(case, "group_failed_earlier") + local_results.append(skipped) + write_result(skipped) + continue + + _tprint(f" [W{worker_id}] {case.case_id} (expect: {case.expected})" + f" route=({layout.start_x},{layout.start_y},{layout.start_z})" + f" -> ({layout.end_x},{layout.end_y},{layout.end_z})") + + current_ctx = ensure_worker() + if current_ctx is None: + result = make_harness_result( + case, + error_kind="harness_worker_launch_failed", + log_excerpt=" Harness: failed to launch worker session", + session=worker_session, + ) + else: + reset_ok = False + try: + reset_ok = reset_worker_state(current_ctx, layout) + except Exception: + reset_ok = False + + if not reset_ok: + cleanup_workers([current_ctx]) + ctx = None + current_ctx = ensure_worker() + if current_ctx is not None: + try: + reset_ok = reset_worker_state(current_ctx, layout) + except Exception: + reset_ok = False + + if current_ctx is None: + result = make_harness_result( + case, + error_kind="harness_worker_launch_failed", + log_excerpt=" Harness: failed to relaunch worker session", + session=worker_session, + ) + elif not reset_ok: + result = make_harness_result( + case, + error_kind="harness_start_sync_failed", + log_excerpt=( + " Harness: local MCC position did not stabilize at test start " + f"goal=({layout.start_x + 0.5:.1f},{float(layout.start_y):.1f},{layout.start_z + 0.5:.1f})" + ), + session=current_ctx.session, + log_path=str(current_ctx.mcc.log_file), + ) + else: + result = run_single_test( + case, + layout, + current_ctx.rcon, + current_ctx.mcc, + current_ctx.username, + wait_seconds, + ) + + local_results.append(result) + + status = "OK" if result.matched_expected else "MISMATCH" + _tprint(f" [W{worker_id}] {case.case_id}: " + f"{result.outcome} [{status}]") + if result.log_excerpt: + _tprint(result.log_excerpt) + + if result.outcome in ("reject", "fail") and case.expected == "pass": + failed_groups.add(case.group_key()) + _tprint(f" [W{worker_id}] >> Group failed -- " + f"skipping larger values") + + write_result(result) + + group_queue.task_done() + + with results_lock: + all_results.extend(local_results) + skipped_counter[0] += local_skipped + + _tprint(f" [W{worker_id}] Finished: {len(local_results)} tests, " + f"{local_skipped} skipped") + + +def cleanup_workers(workers: list[WorkerContext]) -> None: + """Shut down all MCC instances.""" + for w in workers: + try: + w.mcc.send("quit") + except Exception: + pass + w.rcon.close() + + time.sleep(2) + + for w in workers: + tmux_session = f"mcc-{w.session}" + subprocess.run( + ["tmux", "kill-session", "-t", tmux_session], + capture_output=True, + ) + + # Clean up pid/meta files + tmpdir = os.environ.get("TMPDIR", "/tmp") + for w in workers: + session_root = Path(tmpdir) / "mcc-debug" / w.session + for f in ["mcc.pid", "session.meta"]: + fpath = session_root / f + if fpath.exists(): + fpath.unlink() + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main() -> None: + parser = argparse.ArgumentParser( + description="Full-coverage parkour test suite", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__.split("Usage:")[0], + ) + parser.add_argument("--list-cases", action="store_true", + help="Print test matrix and exit") + parser.add_argument("--dry-run", action="store_true", + help="Build worlds but don't run MCC navigation") + parser.add_argument("--filter", type=str, default=None, + help="Comma-separated hierarchical filters") + parser.add_argument("--rcon-port", type=int, default=25575) + parser.add_argument("--rcon-password", type=str, default="test123") + parser.add_argument("--username", type=str, default="MCCBot", + help="MCC username (base name when --parallel > 1)") + parser.add_argument("--wait", type=int, default=15, + help="Seconds to wait for navigation per test") + parser.add_argument("--results", type=str, default=None, + help="Path for JSONL results output") + parser.add_argument("--parallel", type=int, default=6, + help="Number of parallel MCC instances (default: 6)") + parser.add_argument("--version", type=str, default="1.21.11-Vanilla", + help="MC server version for auto-launching MCC") + parser.add_argument("--server-port", type=int, default=25565, + help="MC server port for auto-launched clients") + args = parser.parse_args() + + caps = load_capabilities(CAPABILITIES_PATH) + all_cases = derive_test_matrix(caps) + + if args.filter: + all_cases = apply_filters(all_cases, args.filter) + + if args.list_cases: + pass_count = sum(1 for c in all_cases if c.expected == "pass") + reject_count = sum(1 for c in all_cases if c.expected == "reject") + print(f"Total: {len(all_cases)} cases ({pass_count} pass, {reject_count} reject)") + + current_group = "" + for c in all_cases: + group = f"{c.family}/{c.subfamily}" + if group != current_group: + print(f"\n {group}:") + current_group = group + marker = "PASS" if c.expected == "pass" else "REJECT" + quals = [] + if c.delta_y != 0.0: + quals.append(f"dy={c.delta_y:+.0f}") + if c.ceiling_height is not None: + quals.append(f"ceil={c.ceiling_height}") + if c.wall_offset is not None: + quals.append(f"wo={c.wall_offset}") + q = f" ({', '.join(quals)})" if quals else "" + metric = "gap" if c.family != "neo" else "wall" + print(f" {c.case_id:<50} {metric}={c.gap_or_wall} [{marker}]{q}") + return + + try: + rcon = connect_rcon_with_retry( + port=args.rcon_port, + password=args.rcon_password, + timeout_seconds=30.0, + ) + except Exception as exc: + print(f"Harness error: RCON unavailable on localhost:{args.rcon_port}: {exc}") + sys.exit(2) + + rcon.command("difficulty peaceful") + rcon.command("gamerule doMobSpawning false") + rcon.command("time set day") + + builder = WorldBuilder(rcon) + + if args.dry_run: + print("=== DRY RUN: building worlds only ===") + rcon.command(f"gamemode creative {args.username}") + + z_end = builder.compute_z_extent(all_cases) + builder.forceload_region(z_end) + print(" Clearing entire test region...") + builder.clear_entire_region(z_end) + + for i, case in enumerate(all_cases, 1): + layout = builder.build(case) + print(f" {case.case_id}: start=({layout.start_x},{layout.start_y},{layout.start_z})" + f" end=({layout.end_x},{layout.end_y},{layout.end_z})") + + builder.forceload_remove() + rcon.command(f"tp {args.username} {builder.base_x}.5 " + f"{builder.base_y + 5} {builder.Z_START + CLEAR_PADDING}.5") + rcon.close() + print(f"\nBuilt {len(all_cases)} courses.") + return + + run_dir = create_run_dir() + canonical_results_path = run_dir / "results.jsonl" + results_paths = [canonical_results_path] + if args.results: + results_paths.append(Path(args.results)) + for path in results_paths: + path.parent.mkdir(parents=True, exist_ok=True) + + # Phase 1: Clear region and build all courses up front + print("=" * 60) + print(" Phase 1: Building all courses") + print("=" * 60) + print(f" Run artifacts: {run_dir}") + print(f" Results JSONL: {canonical_results_path}") + if args.results: + print(f" External Results JSONL: {Path(args.results)}") + + rcon.command(f"gamemode creative {args.username}") + + z_end = builder.compute_z_extent(all_cases) + builder.forceload_region(z_end) + print(" Clearing entire test region...") + builder.clear_entire_region(z_end) + + layouts: list[tuple[TestCase, CourseLayout]] = [] + for i, case in enumerate(all_cases, 1): + layout = builder.build(case) + layouts.append((case, layout)) + print(f" [{i}/{len(all_cases)}] {case.case_id}: " + f"({layout.start_x},{layout.start_y},{layout.start_z}) -> " + f"({layout.end_x},{layout.end_y},{layout.end_z})") + + print(f"\n Built {len(layouts)} courses.") + + # Phase 2: Run tests + n_parallel = args.parallel + try: + if n_parallel > 1: + _run_parallel(layouts, rcon, args, results_paths, run_dir, n_parallel) + else: + _run_serial(layouts, rcon, args, results_paths, run_dir) + finally: + builder.forceload_remove() + + +def _run_serial( + layouts: list[tuple[TestCase, CourseLayout]], + rcon: RconClient, + args: argparse.Namespace, + results_paths: list[Path], + run_dir: Path, +) -> None: + """Original serial test execution path.""" + session = resolve_session() + mcc = MccClient(session) + + print() + print("=" * 60) + print(" Phase 2: Running tests (serial)") + print("=" * 60) + print(f" Cases: {len(layouts)}") + print(f" Username: {args.username}") + print(f" Session: {session}") + print(f" Wait: {args.wait}s per test") + print() + + results: list[TestResult] = [] + failed_groups: set[tuple] = set() + skipped = 0 + serial_ctx = WorkerContext( + worker_id=0, + username=args.username, + session=session, + rcon=rcon, + mcc=mcc, + ) + + for i, (case, layout) in enumerate(layouts, 1): + if should_skip(case, failed_groups): + skipped += 1 + print(f" [{i}/{len(layouts)}] {case.case_id} -- SKIPPED (group already failed)") + skipped_result = make_skip_result(case, "group_failed_earlier") + results.append(skipped_result) + append_jsonl_record(results_paths, result_to_record(skipped_result)) + continue + + print(f"\n--- [{i}/{len(layouts)}] {case.case_id} (expect: {case.expected}) ---") + print(f" Route: ({layout.start_x},{layout.start_y},{layout.start_z}) -> " + f"({layout.end_x},{layout.end_y},{layout.end_z})") + + if reset_worker_state(serial_ctx, layout): + result = run_single_test( + case, layout, rcon, mcc, args.username, args.wait, + ) + else: + result = make_harness_result( + case, + error_kind="harness_start_sync_failed", + log_excerpt=( + " Harness: local MCC position did not stabilize at test start " + f"goal=({layout.start_x + 0.5:.1f},{float(layout.start_y):.1f},{layout.start_z + 0.5:.1f})" + ), + session=session, + log_path=str(mcc.log_file), + ) + results.append(result) + + status = "OK" if result.matched_expected else "MISMATCH" + print(f" Outcome: {result.outcome} [{status}]") + if result.log_excerpt: + print(result.log_excerpt) + + if result.outcome in ("reject", "fail") and case.expected == "pass": + failed_groups.add(case.group_key()) + print(f" >> Group failed at {case.family}/{case.subfamily} " + f"gap/wall={case.gap_or_wall} -- skipping larger values") + + append_jsonl_record(results_paths, result_to_record(result)) + + rcon.close() + _print_summary(results, skipped, run_dir) + + +def _run_parallel( + layouts: list[tuple[TestCase, CourseLayout]], + rcon: RconClient, + args: argparse.Namespace, + results_paths: list[Path], + run_dir: Path, + n_parallel: int, +) -> None: + """Parallel test execution with streaming worker launch. + + Each worker thread handles its own lifecycle: launch MCC, wait for join, + op/debug-on, then immediately start pulling work from the shared queue. + No need to wait for all workers before starting tests. + """ + # Group cases by group_key for atomic distribution + groups: dict[tuple, list[tuple[TestCase, CourseLayout]]] = {} + for case, layout in layouts: + groups.setdefault(case.group_key(), []).append((case, layout)) + + group_q: queue.Queue = queue.Queue() + for key, items in groups.items(): + group_q.put((key, items)) + + print() + print("=" * 60) + print(f" Launching {n_parallel} workers ({len(groups)} groups, " + f"{len(layouts)} cases)") + print("=" * 60) + print(f" Wait: {args.wait}s per test") + print() + + all_results: list[TestResult] = [] + results_lock = threading.Lock() + workers_registry: list[WorkerContext] = [] + registry_lock = threading.Lock() + threads: list[threading.Thread] = [] + skipped_counter = [0] + run_token = make_parallel_run_token() + + for i in range(1, n_parallel + 1): + t = threading.Thread( + target=worker_loop, + args=(i, args.username, run_token, args.version, args.server_port, + args.rcon_port, args.rcon_password, + group_q, all_results, results_lock, + args.wait, results_paths, + workers_registry, registry_lock, skipped_counter), + daemon=True, + ) + threads.append(t) + t.start() + + for t in threads: + t.join() + + rcon.close() + + print() + print("=" * 60) + print(" Cleanup") + print("=" * 60) + cleanup_workers(workers_registry) + print(" All workers stopped.") + + _print_summary(all_results, skipped=skipped_counter[0], run_dir=run_dir) + + +def _print_summary(results: list[TestResult], skipped: int, run_dir: Path) -> None: + print("\n" + "=" * 60) + print(" SUMMARY") + print("=" * 60) + + passed = [r for r in results if r.matched_expected] + failed = [r for r in results if not r.matched_expected] + summary = summarize_results([result_to_record(r) for r in results]) + write_summary_files(run_dir, summary) + + print(f"\n {len(passed)}/{len(results)} matched expectations") + if skipped: + print(f" {skipped} cases skipped (stop-at-first-failure)") + print(f" Summary dir: {run_dir}") + + if failed: + print(f"\n MISMATCHES ({len(failed)}):") + for r in failed: + print(f" {r.case.case_id}: expected={r.case.expected} got={r.outcome}") + + sys.exit(0 if not failed else 1) + + +if __name__ == "__main__": + main() diff --git a/tools/test-parkour.sh b/tools/test-parkour.sh new file mode 100644 index 0000000000..6cf16d859b --- /dev/null +++ b/tools/test-parkour.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Full-coverage parkour test suite for MCC pathfinding +# Thin wrapper around test-parkour.py +# Usage: source tools/mcc-env.sh && bash tools/test-parkour.sh [args...] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +exec python3 "$SCRIPT_DIR/test-parkour.py" "$@" diff --git a/tools/test-pathing-jump-combos.sh b/tools/test-pathing-jump-combos.sh new file mode 100644 index 0000000000..d57270a6e4 --- /dev/null +++ b/tools/test-pathing-jump-combos.sh @@ -0,0 +1,411 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +SESSION="${SESSION:-mcc-pathing-jump-combos}" +USERNAME="${USERNAME:-MCCBot}" + +SESSION_ROOT="$(_mcc_session_root "$SESSION")" +LOG="$(_mcc_session_log_file "$SESSION")" +PLANNER_CONTRACTS="$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json" +TIMING_BUDGETS="$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json" + +PASSED_CASES=() +FAILED_CASES=() + +cleanup() { + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true +} + +trap cleanup EXIT + +send_mcc() { + mcc-cmd --session "$SESSION" "$1" +} + +log_line_count() { + if [[ -f "$LOG" ]]; then + wc -l < "$LOG" + else + echo 0 + fi +} + +log_since() { + local from_line="$1" + if [[ ! -f "$LOG" ]]; then + return + fi + + tail -n +"$((from_line + 1))" "$LOG" +} + +log_since_clean() { + log_since "$1" | sed 's/\x1b\[[0-9;]*m//g' +} + +wait_for_log() { + local pattern="$1" + local from_line="${2:-0}" + local timeout="${3:-30}" + + for _ in $(seq 1 "$timeout"); do + if log_since_clean "$from_line" | grep -Fq "$pattern"; then + return 0 + fi + sleep 1 + done + + return 1 +} + +wait_for_navigation() { + local from_line="$1" + local timeout="${2:-45}" + local saw_start=0 + + for _ in $(seq 1 "$timeout"); do + local recent + recent="$(log_since_clean "$from_line")" + + if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then + saw_start=1 + fi + + if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then + return 0 + fi + + if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + sleep 1 + done + + echo "Timed out waiting for navigation completion" >&2 + log_since_clean "$from_line" >&2 + return 1 +} + +count_replans_since() { + local from_line="$1" + log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true +} + +assert_no_replans_since() { + local from_line="$1" + local count + count="$(count_replans_since "$from_line")" + if [[ "$count" != "0" ]]; then + echo "Expected 0 replans, saw $count" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +assert_no_partial_since() { + local from_line="$1" + if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then + echo "Expected full success path, saw partial path" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +debug_state_snapshot() { + local label="$1" + local from_line + from_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$from_line" 10 + echo "" + echo "=== Debug state: $label ===" + log_since_clean "$from_line" +} + +capture_debug_state_before_route() { + debug_state_snapshot "before route - $1" +} + +capture_debug_state_after_route() { + debug_state_snapshot "after route - $1" +} + +capture_debug_location() { + local start_line + start_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$start_line" 10 + extract_last_location "$start_line" +} + +wait_for_location_in_block() { + local expected_x="$1" + local expected_y="$2" + local expected_z="$3" + local timeout="${4:-10}" + + for _ in $(seq 1 "$timeout"); do + local actual_x actual_y actual_z + read -r actual_x actual_y actual_z <<< "$(capture_debug_location)" + if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05: + raise SystemExit(0) +raise SystemExit(1) +PY + then + return 0 + fi + sleep 1 + done + + echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2 + return 1 +} + +prepare_independent_route() { + local label="$1" + 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 $start_yaw $start_pitch" >/dev/null + wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10 +} + +extract_last_location() { + local from_line="${1:-0}" + + python3 - "$LOG" "$from_line" <<'PY' +import pathlib +import re +import sys + +log_path = pathlib.Path(sys.argv[1]) +from_line = int(sys.argv[2]) +text = log_path.read_text(errors="ignore") +text = "\n".join(text.splitlines()[from_line:]) +text = re.sub(r"\x1b\[[0-9;]*m", "", text) +matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text) +if not matches: + raise SystemExit("No Location line found in MCC log") +x, y, z = matches[-1] +print(f"{x} {y} {z}") +PY +} + +assert_inside_goal_block() { + local actual_x="$1" + local actual_y="$2" + local actual_z="$3" + local expected_x="$4" + local expected_y="$5" + local expected_z="$6" + + python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05: + raise SystemExit( + f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})" + ) +PY +} + +print_summary() { + local header="$1" + + echo "" + echo "----- $header -----" + if [[ -f "$LOG" ]]; then + tail -n 40 "$LOG" | sed 's/\x1b\[[0-9;]*m//g' + else + echo "(no log available yet)" + fi +} + +fill_box() { + mc-rcon "fill $1 $2 $3 $4 $5 $6 $7" >/dev/null +} + +set_stone() { + mc-rcon "setblock $1 $2 $3 stone" >/dev/null +} + +run_accepted_route() { + local scenario_id="$1" + local label="$2" + local start_x="$3" + local start_y="$4" + local start_z="$5" + local goal_x="$6" + local goal_y="$7" + local goal_z="$8" + local start_yaw="${9:-270}" + local start_pitch="${10:-0}" + local timeout="${11:-45}" + + prepare_independent_route "$label" "$start_x" "$start_y" "$start_z" "$start_yaw" "$start_pitch" + capture_debug_state_before_route "$label" + + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind $goal_x $goal_y $goal_z" + wait_for_navigation "$start_line" "$timeout" + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + python3 "$REPO_ROOT/tools/pathing_contract_report.py" \ + --scenario-id "$scenario_id" \ + --log-file "$LOG" \ + --from-line "$start_line" \ + --planner-contracts "$PLANNER_CONTRACTS" \ + --timing-budgets "$TIMING_BUDGETS" + capture_debug_state_after_route "$label" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z" + print_summary "$label" +} + +run_case() { + local label="$1" + shift + + echo "" + echo "== $label ==" + set +e + ( + set -e + "$@" + ) + local status=$? + set -e + + if [[ $status -eq 0 ]]; then + PASSED_CASES+=("$label") + echo "RESULT: PASS - $label" + else + FAILED_CASES+=("$label") + echo "RESULT: FAIL - $label" + fi +} + +start_mcc() { + mkdir -p "$SESSION_ROOT" + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true + mcc-build >/dev/null + mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null + wait_for_log "Server was successfully joined." 0 40 + send_mcc "debug on" +} + +scenario_repeated_cardinal_parkour() { + fill_box 578 79 578 590 79 582 air + fill_box 578 80 578 590 90 582 air + set_stone 580 79 580 + set_stone 582 79 580 + 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" "270" +} + +scenario_repeated_diagonal_parkour() { + fill_box 598 79 598 608 79 608 air + fill_box 598 80 598 608 90 608 air + set_stone 600 79 600 + 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" "315" +} + +scenario_obstructed_parkour_turn_mix() { + fill_box 618 79 618 628 79 624 air + fill_box 618 80 618 628 90 624 air + set_stone 620 79 620 + set_stone 622 79 620 + set_stone 622 79 621 + set_stone 624 79 621 + set_stone 624 79 622 + set_stone 626 79 622 + set_stone 620 80 621 + 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" "270" +} + +scenario_parkour_ascend_descend_chain() { + fill_box 638 79 618 650 80 622 air + fill_box 638 81 618 650 92 622 air + set_stone 640 79 620 + set_stone 642 80 620 + 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" "270" +} + +scenario_diagonal_ascend_descend_chain() { + fill_box 678 79 618 686 80 626 air + fill_box 678 81 618 686 92 626 air + set_stone 680 79 620 + set_stone 681 80 621 + 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" "315" +} + +start_mcc + +mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true +mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true +mc-rcon "time set day" >/dev/null 2>&1 || true + +run_case "Repeated jump - cardinal parkour chain" scenario_repeated_cardinal_parkour +run_case "Repeated jump - diagonal parkour chain" scenario_repeated_diagonal_parkour +run_case "Obstructed jump mix - repeated parkour L-turns" scenario_obstructed_parkour_turn_mix +run_case "Vertical jump mix - parkour ascend descend chain" scenario_parkour_ascend_descend_chain +run_case "Diagonal vertical mix - ascend descend chain" scenario_diagonal_ascend_descend_chain + +echo "" +echo "Jump combo summary:" +for label in "${PASSED_CASES[@]}"; do + echo " PASS $label" +done +for label in "${FAILED_CASES[@]}"; do + echo " FAIL $label" +done + +if [[ ${#FAILED_CASES[@]} -ne 0 ]]; then + exit 1 +fi + +echo "" +echo "Pathing jump-combo suite complete." diff --git a/tools/test-pathing-long-routes.sh b/tools/test-pathing-long-routes.sh new file mode 100644 index 0000000000..4140025338 --- /dev/null +++ b/tools/test-pathing-long-routes.sh @@ -0,0 +1,467 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +SESSION="${SESSION:-mcc-pathing-long-routes}" +USERNAME="${USERNAME:-MCCBot}" + +SESSION_ROOT="$(_mcc_session_root "$SESSION")" +LOG="$(_mcc_session_log_file "$SESSION")" +PLANNER_CONTRACTS="$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-planner-contracts.json" +TIMING_BUDGETS="$REPO_ROOT/MinecraftClient.Tests/TestData/Pathing/pathing-timing-budgets.json" + +cleanup() { + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true +} + +trap cleanup EXIT + +send_mcc() { + mcc-cmd --session "$SESSION" "$1" +} + +log_line_count() { + if [[ -f "$LOG" ]]; then + wc -l < "$LOG" + else + echo 0 + fi +} + +log_since() { + local from_line="$1" + if [[ ! -f "$LOG" ]]; then + return + fi + + tail -n +"$((from_line + 1))" "$LOG" +} + +wait_for_log() { + local pattern="$1" + local from_line="${2:-0}" + local timeout="${3:-30}" + + for _ in $(seq 1 "$timeout"); do + if log_since_clean "$from_line" | grep -Fq "$pattern"; then + return 0 + fi + sleep 1 + done + + return 1 +} + +wait_for_navigation() { + local from_line="$1" + local timeout="${2:-45}" + local saw_start=0 + + for _ in $(seq 1 "$timeout"); do + local recent + recent="$(log_since_clean "$from_line")" + + if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then + saw_start=1 + fi + + if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then + return 0 + fi + + if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + sleep 1 + done + + echo "Timed out waiting for navigation completion" >&2 + log_since_clean "$from_line" >&2 + return 1 +} + +log_since_clean() { + log_since "$1" | sed 's/\x1b\[[0-9;]*m//g' +} + +count_replans_since() { + local from_line="$1" + log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true +} + +assert_no_replans_since() { + local from_line="$1" + local count + count="$(count_replans_since "$from_line")" + if [[ "$count" != "0" ]]; then + echo "Expected 0 replans, saw $count" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +assert_no_partial_since() { + local from_line="$1" + if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then + echo "Expected full success path, saw partial path" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +debug_state_snapshot() { + local label="$1" + local from_line + from_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$from_line" 10 + echo "" + echo "=== Debug state: $label ===" + log_since_clean "$from_line" +} + +capture_debug_state_before_route() { + debug_state_snapshot "before route - $1" +} + +capture_debug_state_after_route() { + debug_state_snapshot "after route - $1" +} + +prepare_independent_route() { + local label="$1" + 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 $start_yaw $start_pitch" >/dev/null + wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10 +} + +capture_debug_location() { + local start_line + start_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$start_line" 10 + extract_last_location "$start_line" +} + +wait_for_location_in_block() { + local expected_x="$1" + local expected_y="$2" + local expected_z="$3" + local timeout="${4:-10}" + + for _ in $(seq 1 "$timeout"); do + local actual_x actual_y actual_z + read -r actual_x actual_y actual_z <<< "$(capture_debug_location)" + if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05: + raise SystemExit(0) +raise SystemExit(1) +PY + then + return 0 + fi + sleep 1 + done + + echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2 + return 1 +} + +extract_last_location() { + local from_line="${1:-0}" + + python3 - "$LOG" "$from_line" <<'PY' +import pathlib +import re +import sys + +log_path = pathlib.Path(sys.argv[1]) +from_line = int(sys.argv[2]) +text = log_path.read_text(errors="ignore") +text = "\n".join(text.splitlines()[from_line:]) +text = re.sub(r"\x1b\[[0-9;]*m", "", text) +matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text) +if not matches: + raise SystemExit("No Location line found in MCC log") +x, y, z = matches[-1] +print(f"{x} {y} {z}") +PY +} + +assert_inside_goal_block() { + local actual_x="$1" + local actual_y="$2" + local actual_z="$3" + local expected_x="$4" + local expected_y="$5" + local expected_z="$6" + + python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05: + raise SystemExit( + f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})" + ) +PY +} + +print_summary() { + local header="$1" + + echo "" + echo "----- $header -----" + if [[ -f "$LOG" ]]; then + tail -n 40 "$LOG" | sed 's/\x1b\[[0-9;]*m//g' + else + echo "(no log available yet)" + fi +} + +fill_box() { + mc-rcon "fill $1 $2 $3 $4 $5 $6 $7" >/dev/null +} + +set_stone() { + mc-rcon "setblock $1 $2 $3 stone" >/dev/null +} + +run_accepted_route() { + local scenario_id="$1" + local label="$2" + local start_x="$3" + local start_y="$4" + local start_z="$5" + local goal_x="$6" + local goal_y="$7" + local goal_z="$8" + local start_yaw="${9:-270}" + local start_pitch="${10:-0}" + local timeout="${11:-45}" + + prepare_independent_route "$label" "$start_x" "$start_y" "$start_z" "$start_yaw" "$start_pitch" + capture_debug_state_before_route "$label" + + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind $goal_x $goal_y $goal_z" + wait_for_navigation "$start_line" "$timeout" + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + python3 "$REPO_ROOT/tools/pathing_contract_report.py" \ + --scenario-id "$scenario_id" \ + --log-file "$LOG" \ + --from-line "$start_line" \ + --planner-contracts "$PLANNER_CONTRACTS" \ + --timing-budgets "$TIMING_BUDGETS" + capture_debug_state_after_route "$label" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z" + print_summary "$label" +} + +start_mcc() { + mkdir -p "$SESSION_ROOT" + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true + mcc-build >/dev/null + mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null + wait_for_log "Server was successfully joined." 0 40 + send_mcc "debug on" +} + +run_same_move_routes() { + echo "== 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" "270" + + fill_box 318 79 318 330 79 330 air + fill_box 318 80 318 330 90 330 air + set_stone 320 79 320 + set_stone 321 79 321 + set_stone 322 79 322 + set_stone 323 79 323 + set_stone 324 79 324 + 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" "315" + + fill_box 338 79 338 347 85 342 air + fill_box 338 80 338 347 90 342 air + fill_box 340 79 339 340 79 341 stone + fill_box 341 80 339 341 80 341 stone + fill_box 342 81 339 342 81 341 stone + 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" "270" + + fill_box 360 79 358 369 85 362 air + fill_box 360 80 358 369 90 362 air + fill_box 362 84 359 362 84 361 stone + fill_box 363 83 359 363 83 361 stone + fill_box 364 82 359 364 82 361 stone + 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" "270" + + fill_box 378 79 378 390 79 382 air + fill_box 378 80 378 390 90 382 air + set_stone 380 79 380 + set_stone 382 79 380 + 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" "270" +} + +run_mixed_move_routes() { + echo "== Mixed move routes ==" + + fill_box 398 79 398 410 79 406 air + fill_box 398 80 398 410 90 406 air + set_stone 400 79 400 + set_stone 401 79 400 + set_stone 402 79 400 + set_stone 402 79 401 + set_stone 402 79 402 + set_stone 404 79 402 + set_stone 405 79 402 + set_stone 406 79 402 + set_stone 406 79 403 + 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" "270" + + fill_box 418 79 418 430 82 424 air + fill_box 418 80 418 430 92 424 air + set_stone 420 79 420 + set_stone 421 79 421 + set_stone 422 79 422 + set_stone 423 80 422 + set_stone 424 81 422 + set_stone 425 81 422 + 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" "315" + + fill_box 438 79 438 450 82 442 air + fill_box 438 80 438 450 92 442 air + set_stone 440 79 440 + set_stone 441 79 440 + set_stone 442 80 440 + set_stone 443 81 440 + set_stone 444 81 440 + 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" "270" +} + +run_turn_density_routes() { + echo "== Turn density routes ==" + + fill_box 458 79 458 468 79 468 air + fill_box 458 80 458 468 90 468 air + set_stone 460 79 460 + set_stone 461 79 460 + set_stone 461 79 461 + set_stone 462 79 462 + set_stone 463 79 462 + set_stone 463 79 463 + set_stone 464 79 464 + 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" "270" +} + +run_speed_carry_routes() { + echo "== Speed carry routes ==" + + fill_box 478 79 478 490 83 482 air + fill_box 478 80 478 490 94 482 air + set_stone 480 79 480 + set_stone 481 79 480 + set_stone 482 80 480 + set_stone 483 80 480 + set_stone 484 81 480 + set_stone 485 81 480 + 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" "270" + + fill_box 498 79 498 510 82 502 air + fill_box 498 80 498 510 94 502 air + set_stone 500 82 500 + set_stone 501 82 500 + set_stone 502 81 500 + set_stone 503 81 500 + set_stone 504 80 500 + 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" "270" + + fill_box 518 79 518 532 79 522 air + fill_box 518 80 518 532 90 522 air + set_stone 520 79 520 + set_stone 521 79 520 + set_stone 523 79 520 + set_stone 524 79 520 + 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" "270" +} + +start_mcc + +mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true +mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true +mc-rcon "time set day" >/dev/null 2>&1 || true + +run_same_move_routes +run_mixed_move_routes +run_turn_density_routes +run_speed_carry_routes + +mcc-kill --session "$SESSION" >/dev/null 2>&1 || true + +echo "" +echo "Pathing long-route suite complete." diff --git a/tools/test-pathing-template-regressions.sh b/tools/test-pathing-template-regressions.sh new file mode 100644 index 0000000000..061fcbac60 --- /dev/null +++ b/tools/test-pathing-template-regressions.sh @@ -0,0 +1,479 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +SESSION="${SESSION:-mcc-pathing-template}" +USERNAME="${USERNAME:-MCCBot}" + +SESSION_ROOT="$(_mcc_session_root "$SESSION")" +LOG="$(_mcc_session_log_file "$SESSION")" + +cleanup() { + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true +} + +trap cleanup EXIT + +send_mcc() { + mcc-cmd --session "$SESSION" "$1" +} + +log_line_count() { + if [[ -f "$LOG" ]]; then + wc -l < "$LOG" + else + echo 0 + fi +} + +log_since() { + local from_line="$1" + if [[ ! -f "$LOG" ]]; then + return + fi + + tail -n +"$((from_line + 1))" "$LOG" +} + +wait_for_log() { + local pattern="$1" + local from_line="${2:-0}" + local timeout="${3:-30}" + + for _ in $(seq 1 "$timeout"); do + if log_since_clean "$from_line" | grep -Fq "$pattern"; then + return 0 + fi + sleep 1 + done + + return 1 +} + +wait_for_navigation() { + local from_line="$1" + local timeout="${2:-25}" + local saw_start=0 + + for _ in $(seq 1 "$timeout"); do + local recent + recent="$(log_since_clean "$from_line")" + + if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then + saw_start=1 + fi + + if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then + return 0 + fi + + if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + sleep 1 + done + + echo "Timed out waiting for navigation completion" >&2 + log_since_clean "$from_line" >&2 + return 1 +} + +wait_for_failure_signal() { + local from_line="$1" + local timeout="${2:-20}" + + for _ in $(seq 1 "$timeout"); do + local recent + recent="$(log_since_clean "$from_line")" + + if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then + return 0 + fi + + sleep 1 + done + + return 1 +} + +log_since_clean() { + log_since "$1" | sed 's/\x1b\[[0-9;]*m//g' +} + +count_replans_since() { + local from_line="$1" + log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true +} + +assert_no_replans_since() { + local from_line="$1" + local count + count="$(count_replans_since "$from_line")" + if [[ "$count" != "0" ]]; then + echo "Expected 0 replans, saw $count" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +assert_no_partial_since() { + local from_line="$1" + if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then + echo "Expected full success path, saw partial path" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +debug_state_snapshot() { + local label="$1" + local from_line + from_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$from_line" 10 + echo "" + echo "=== Debug state: $label ===" + log_since_clean "$from_line" +} + +capture_debug_state_before_route() { + debug_state_snapshot "before route - $1" +} + +capture_debug_state_after_route() { + debug_state_snapshot "after route - $1" +} + +prepare_independent_route() { + local label="$1" + 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 $start_yaw $start_pitch" >/dev/null + wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10 +} + +assert_direct_rejection_since() { + local from_line="$1" + if ! log_since_clean "$from_line" | grep -Eq "No path found|\[Navigate\] A\* result: Failed"; then + echo "Expected direct rejection before navigation execution" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi + if log_since_clean "$from_line" | grep -Fq "[PathMgr] Navigation started"; then + echo "Expected rejection before navigation started" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi + assert_no_replans_since "$from_line" +} + +extract_last_location() { + local from_line="${1:-0}" + + python3 - "$LOG" "$from_line" <<'PY' +import pathlib +import re +import sys + +log_path = pathlib.Path(sys.argv[1]) +from_line = int(sys.argv[2]) +text = log_path.read_text(errors="ignore") +text = "\n".join(text.splitlines()[from_line:]) +text = re.sub(r"\x1b\[[0-9;]*m", "", text) +matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text) +if not matches: + matches = re.findall(r"Segment \d+ complete .* at \(([-,0-9.]+),([-,0-9.]+),([-,0-9.]+)\)", text) +if not matches: + matches = re.findall(r"pos=\(([-,0-9.]+),\s*([-,0-9.]+),\s*([-,0-9.]+)\)", text) +if not matches: + raise SystemExit("No location line found in MCC log") +x, y, z = matches[-1] +print(f"{x} {y} {z}") +PY +} + +assert_inside_goal_block() { + local actual_x="$1" + local actual_y="$2" + local actual_z="$3" + local target_x="$4" + local target_y="$5" + local target_z="$6" + + python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$target_x" "$target_y" "$target_z" +import math +import sys + +ax, ay, az, tx, ty, tz = map(float, sys.argv[1:]) +if math.floor(ax) != int(tx) or math.floor(az) != int(tz) or abs(ay - ty) > 0.05: + raise SystemExit( + f"Expected location inside goal block ({int(tx)}, {ty:.2f}, {int(tz)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})" + ) +PY +} + +print_summary() { + local header="$1" + + echo "" + echo "----- $header -----" + if [[ -f "$LOG" ]]; then + tail -n 40 "$LOG" | sed 's/\x1b\[[0-9;]*m//g' + else + echo "(no log available yet)" + fi +} + +capture_debug_location() { + local start_line + start_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$start_line" 10 + extract_last_location "$start_line" +} + +wait_for_location_in_block() { + local expected_x="$1" + local expected_y="$2" + local expected_z="$3" + local timeout="${4:-10}" + + for _ in $(seq 1 "$timeout"); do + local actual_x actual_y actual_z + read -r actual_x actual_y actual_z <<< "$(capture_debug_location)" + if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05: + raise SystemExit(0) +raise SystemExit(1) +PY + then + return 0 + fi + sleep 1 + done + + echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2 + return 1 +} + +start_mcc() { + mkdir -p "$SESSION_ROOT" + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true + mcc-build >/dev/null + mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null + wait_for_log "Server was successfully joined." 0 40 + send_mcc "debug on" +} + +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" "270" + capture_debug_state_before_route "Flat final stop" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 103 80 100" + wait_for_navigation "$start_line" 30 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Flat final stop" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "103" "80.00" "100" + print_summary "Flat final stop" +} + +run_parkour_into_turn() { + echo "== Parkour into L-turn ==" + mc-rcon "fill 118 79 108 126 79 112 air" >/dev/null + mc-rcon "fill 118 80 108 126 90 112 air" >/dev/null + mc-rcon "setblock 120 79 110 stone" >/dev/null + mc-rcon "setblock 122 79 110 stone" >/dev/null + 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" "270" + capture_debug_state_before_route "Parkour into L-turn" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 122 80 111" + wait_for_navigation "$start_line" 30 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Parkour into L-turn" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "122" "80.00" "111" + print_summary "Parkour into L-turn" +} + +run_side_wall_jump() { + echo "== Rejected 2x1 side-wall jump ==" + mc-rcon "fill 130 79 124 138 79 132 air" >/dev/null + mc-rcon "fill 130 80 124 138 84 132 air" >/dev/null + mc-rcon "setblock 131 79 127 stone" >/dev/null + mc-rcon "setblock 133 79 127 stone" >/dev/null + mc-rcon "setblock 132 80 126 stone" >/dev/null + 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" "270" + capture_debug_state_before_route "Rejected 2x1 side-wall jump" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 133 80 127" + + if wait_for_failure_signal "$start_line" 20; then + assert_direct_rejection_since "$start_line" + echo " Pathfinding rejected as expected." + else + echo " Expected rejection but navigation continued." >&2 + log_since "$start_line" >&2 + return 1 + fi + + print_summary "2x1 side-wall rejection" +} + +run_reject_3x1_gap() { + echo "== Rejected 3x1 no-run-up 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" "270" + capture_debug_state_before_route "Rejected 3x1 gap" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 144 81 138" + + if wait_for_failure_signal "$start_line" 20; then + assert_direct_rejection_since "$start_line" + echo " Pathfinding rejected as expected." + else + echo " Expected rejection but navigation continued." >&2 + log_since "$start_line" >&2 + return 1 + fi + + print_summary "3x1 no-run-up rejection" +} + +run_corner_ascend_around_wall() { + echo "== Corner ascend around wall smoke ==" + mc-rcon "fill 188 79 168 194 84 174 air" >/dev/null + mc-rcon "setblock 190 79 170 stone" >/dev/null + 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" "315" + capture_debug_state_before_route "Corner ascend around wall" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 191 81 171" + wait_for_navigation "$start_line" 25 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Corner ascend around wall" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "191" "81.00" "171" + print_summary "Corner ascend around wall" +} + +run_wall_adjacent_descend_smoke() { + echo "== Wall-adjacent descend smoke ==" + mc-rcon "fill 198 79 198 204 84 202 air" >/dev/null + mc-rcon "fill 201 79 199 203 79 201 stone" >/dev/null + mc-rcon "setblock 200 80 200 stone" >/dev/null + mc-rcon "setblock 200 80 199 stone" >/dev/null + mc-rcon "setblock 201 80 199 stone" >/dev/null + 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" "270" + capture_debug_state_before_route "Wall-adjacent descend" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 201 80 200" + wait_for_navigation "$start_line" 25 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Wall-adjacent descend" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "201" "80.00" "200" + print_summary "Wall-adjacent descend" +} + +run_ascend_chain_smoke() { + echo "== Ascend chain smoke ==" + mc-rcon "fill 170 79 160 178 79 168 stone" >/dev/null + mc-rcon "fill 170 80 160 178 85 168 air" >/dev/null + 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" "315" + capture_debug_state_before_route "Ascend chain smoke" + local start_line + start_line="$(log_line_count)" + send_mcc "pathfind 177 83 162" + wait_for_navigation "$start_line" 35 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Ascend chain smoke" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo " Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "177" "83.00" "162" + + print_summary "Ascend chain smoke" +} + +start_mcc + +mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true +mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true +mc-rcon "time set day" >/dev/null 2>&1 || true + +run_flat_final_stop +run_parkour_into_turn +run_side_wall_jump +run_reject_3x1_gap +run_corner_ascend_around_wall +run_wall_adjacent_descend_smoke +run_ascend_chain_smoke + +echo "" +echo "Pathing template regression suite complete." diff --git a/tools/test-pathing-theory-neo-ceiling.sh b/tools/test-pathing-theory-neo-ceiling.sh new file mode 100644 index 0000000000..20c4bb875d --- /dev/null +++ b/tools/test-pathing-theory-neo-ceiling.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" +source "$REPO_ROOT/tools/pathing_live_common.sh" + +SESSION="${SESSION:-$(_mcc_resolve_session)}" +USERNAME="${USERNAME:-MCCBot}" +MANIFEST="$REPO_ROOT/tools/pathing_data/canonical-live-cases.json" +RESULTS_FILE="${RESULTS_FILE:-$(_mcc_session_root "$SESSION")/pathing-live-results.jsonl}" +LOG="$(_mcc_session_log_file "$SESSION")" +RESULTS="" +TEST_NUM=0 +LAST_RESULT="invalid_live_case" + +if [[ "${1:-}" == "--list-cases" ]]; then + manifest_cases_for_query "$MANIFEST" "neo,ceiling" + exit 0 +fi + +mkdir -p "$(dirname "$RESULTS_FILE")" +: > "$RESULTS_FILE" + +setup_neo_wall() { + local wall_width="$1" + local goal_z="$2" + mc-rcon "fill 95 79 95 115 90 115 air" >/dev/null + mc-rcon "setblock 100 79 100 stone" >/dev/null + mc-rcon "fill 101 79 100 101 79 $((99 + wall_width)) stone" >/dev/null + mc-rcon "setblock 102 79 ${goal_z} stone" >/dev/null +} + +setup_ceiling_headhitter() { + local goal_x="$1" + local ceiling_y="$2" + mc-rcon "fill 95 79 95 115 90 105 air" >/dev/null + mc-rcon "setblock 100 79 100 stone" >/dev/null + mc-rcon "setblock ${goal_x} 79 100 stone" >/dev/null + mc-rcon "fill 100 ${ceiling_y} 100 ${goal_x} ${ceiling_y} 100 stone" >/dev/null +} + +run_manifest_case() { + local case_id="$1" + local case_json + case_json="$(manifest_case_json "$MANIFEST" "$case_id")" + + read -r world_recipe start_x start_y start_z goal_x goal_y goal_z ceiling_height wall_width < <( + python3 - "$case_json" <<'PY' +import json +import sys + +row = json.loads(sys.argv[1]) +print( + row["world_recipe_id"], + row["start"]["x"], + row["start"]["y"], + row["start"]["z"], + row["goal"]["x"], + row["goal"]["y"], + row["goal"]["z"], + row.get("ceiling_height", "null"), + row.get("wall_width", "null"), +) +PY + ) + + case "$world_recipe" in + neo-wall) + setup_neo_wall "${wall_width%.*}" "${goal_z%.*}" + ;; + ceiling-headhitter) + setup_ceiling_headhitter "${goal_x%.*}" "${ceiling_height%.*}" + ;; + *) + echo "Unsupported world recipe for theory neo/ceiling suite: $world_recipe" >&2 + return 1 + ;; + esac + + run_test "$case_id" "${start_x%.*}" "${start_y%.*}" "${start_z%.*}" "${goal_x%.*}" "${goal_y%.*}" "${goal_z%.*}" + record_live_result "$RESULTS_FILE" "$case_json" "$LAST_RESULT" "$LOG" +} + +echo "========================================" +echo " Theory-Aligned Neo And Ceiling Suite" +echo "========================================" + +while IFS= read -r case_id; do + run_manifest_case "$case_id" +done < <(manifest_cases_for_query "$MANIFEST" "neo,ceiling") + +echo "" +echo "========================================" +echo " SUMMARY" +echo "========================================" +echo -e "$RESULTS" diff --git a/tools/test-transition-braking.sh b/tools/test-transition-braking.sh new file mode 100644 index 0000000000..9aedf38c34 --- /dev/null +++ b/tools/test-transition-braking.sh @@ -0,0 +1,293 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" + +VERSION="${1:-1.21.11-Vanilla}" +SESSION="${SESSION:-mcc-brake-test}" +USERNAME="${USERNAME:-MCCBot}" + +SESSION_ROOT="$(_mcc_session_root "$SESSION")" +LOG="$(_mcc_session_log_file "$SESSION")" + +cleanup() { + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true +} + +trap cleanup EXIT + +send_mcc() { + mcc-cmd --session "$SESSION" "$1" +} + +log_line_count() { + if [[ -f "$LOG" ]]; then + wc -l < "$LOG" + else + echo 0 + fi +} + +log_since() { + local from_line="$1" + if [[ ! -f "$LOG" ]]; then + return + fi + + tail -n +"$((from_line + 1))" "$LOG" +} + +wait_for_log() { + local pattern="$1" + local from_line="${2:-0}" + local timeout="${3:-30}" + + for _ in $(seq 1 "$timeout"); do + if log_since_clean "$from_line" | grep -Fq "$pattern"; then + return 0 + fi + sleep 1 + done + + return 1 +} + +wait_for_navigation() { + local from_line="$1" + local timeout="${2:-20}" + local saw_start=0 + + for _ in $(seq 1 "$timeout"); do + local recent + recent="$(log_since_clean "$from_line")" + + if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then + saw_start=1 + fi + + if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then + return 0 + fi + + if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then + echo "$recent" >&2 + return 1 + fi + + sleep 1 + done + + echo "Timed out waiting for navigation completion" >&2 + log_since_clean "$from_line" >&2 + return 1 +} + +log_since_clean() { + log_since "$1" | sed 's/\x1b\[[0-9;]*m//g' +} + +count_replans_since() { + local from_line="$1" + log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true +} + +assert_no_replans_since() { + local from_line="$1" + local count + count="$(count_replans_since "$from_line")" + if [[ "$count" != "0" ]]; then + echo "Expected 0 replans, saw $count" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +assert_no_partial_since() { + local from_line="$1" + if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then + echo "Expected full success path, saw partial path" >&2 + log_since_clean "$from_line" >&2 + return 1 + fi +} + +debug_state_snapshot() { + local label="$1" + local from_line + from_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$from_line" 10 + echo "" + echo "=== Debug state: $label ===" + log_since_clean "$from_line" +} + +capture_debug_state_before_route() { + debug_state_snapshot "before route - $1" +} + +capture_debug_state_after_route() { + debug_state_snapshot "after route - $1" +} + +prepare_independent_route() { + local label="$1" + 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 $start_yaw $start_pitch" >/dev/null + wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10 +} + +extract_last_location() { + local from_line="${1:-0}" + + python3 - "$LOG" "$from_line" <<'PY' +import pathlib +import re +import sys + +log_path = pathlib.Path(sys.argv[1]) +from_line = int(sys.argv[2]) +text = log_path.read_text(errors="ignore") +text = "\n".join(text.splitlines()[from_line:]) +text = re.sub(r"\x1b\[[0-9;]*m", "", text) +matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text) +if not matches: + raise SystemExit("No Location line found in MCC log") +x, y, z = matches[-1] +print(f"{x} {y} {z}") +PY +} + +assert_inside_goal_block() { + local actual_x="$1" + local actual_y="$2" + local actual_z="$3" + local expected_x="$4" + local expected_y="$5" + local expected_z="$6" + + python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05: + raise SystemExit( + f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})" + ) +PY +} + +capture_debug_location() { + local start_line + start_line="$(log_line_count)" + send_mcc "debug state" + wait_for_log "Location" "$start_line" 10 + extract_last_location "$start_line" +} + +wait_for_location_in_block() { + local expected_x="$1" + local expected_y="$2" + local expected_z="$3" + local timeout="${4:-10}" + + for _ in $(seq 1 "$timeout"); do + local actual_x actual_y actual_z + read -r actual_x actual_y actual_z <<< "$(capture_debug_location)" + if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" +import math +import sys + +ax, ay, az, ex, ey, ez = map(float, sys.argv[1:]) +if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05: + raise SystemExit(0) +raise SystemExit(1) +PY + then + return 0 + fi + sleep 1 + done + + echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2 + return 1 +} + +start_mcc() { + mcc-kill --session "$SESSION" >/dev/null 2>&1 || true + mkdir -p "$SESSION_ROOT" + mcc-build >/dev/null + mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --no-build --debug-on >/dev/null + wait_for_log "Server was successfully joined." 0 30 + send_mcc "debug on" +} + +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" "270" + capture_debug_state_before_route "Flat final stop" + local start_line + start_line="$(log_line_count)" + send_mcc "goto 103 80 100" + wait_for_navigation "$start_line" 20 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Flat final stop" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo "Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "103" "80.00" "100" +} + +run_parkour_into_turn() { + echo "== Parkour into turn ==" + mc-rcon "fill 118 79 108 126 79 112 air" >/dev/null + mc-rcon "fill 118 80 108 126 85 112 air" >/dev/null + 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" "270" + capture_debug_state_before_route "Parkour into turn" + local start_line + start_line="$(log_line_count)" + send_mcc "goto 123 80 111" + wait_for_navigation "$start_line" 20 + assert_no_partial_since "$start_line" + assert_no_replans_since "$start_line" + capture_debug_state_after_route "Parkour into turn" + + local x y z + read -r x y z <<< "$(capture_debug_location)" + echo "Final location: $x $y $z" + assert_inside_goal_block "$x" "$y" "$z" "123" "80.00" "111" +} + +start_mcc + +mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true +mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true +mc-rcon "time set day" >/dev/null 2>&1 || true + +run_flat_final_stop +run_parkour_into_turn + +echo "All transition braking checks passed." diff --git a/tools/testdata/pathing-contract-report.sample.log b/tools/testdata/pathing-contract-report.sample.log new file mode 100644 index 0000000000..0ee66a1e5f --- /dev/null +++ b/tools/testdata/pathing-contract-report.sample.log @@ -0,0 +1,4 @@ +[PathMetric] routeStart segments=4 +[PathMetric] segmentComplete index=0 move=Parkour ticks=17 +[PathMetric] segmentComplete index=1 move=Parkour ticks=16 +[PathMetric] routeComplete totalTicks=70 replans=0 diff --git a/tools/tests/__init__.py b/tools/tests/__init__.py new file mode 100644 index 0000000000..440b708316 --- /dev/null +++ b/tools/tests/__init__.py @@ -0,0 +1 @@ +"""Test package for Python tooling.""" diff --git a/tools/tests/test_pathing_canonical_cases.py b/tools/tests/test_pathing_canonical_cases.py new file mode 100644 index 0000000000..14ff9ef30a --- /dev/null +++ b/tools/tests/test_pathing_canonical_cases.py @@ -0,0 +1,57 @@ +import json +import tempfile +import unittest +from pathlib import Path + +from tools.pathing_theory.capabilities import build_momentum_capability_bands +from tools.pathing_theory.canonical import build_canonical_live_cases +from tools.pathing_theory.renderers import write_theory_artifacts +from tools.pathing_theory.simulator import build_theory_cases + + +class CanonicalPathingCaseTests(unittest.TestCase): + def test_build_canonical_live_cases_picks_easy_boundary_and_reject(self) -> None: + canonical_cases = build_canonical_live_cases(build_theory_cases()) + bucket_ids = {case.bucket_id for case in canonical_cases} + + self.assertTrue(all(case.movement_mode == "sprint" for case in canonical_cases)) + self.assertTrue(all(case.momentum_ticks == 12 for case in canonical_cases)) + self.assertIn("linear:flat:sprint:easy", bucket_ids) + self.assertIn("linear:flat:sprint:reject", bucket_ids) + self.assertIn("linear:descend:sprint:boundary", bucket_ids) + self.assertIn("neo:neo:sprint:boundary", bucket_ids) + self.assertIn("ceiling:headhitter:sprint:boundary", bucket_ids) + + def test_write_theory_artifacts_writes_json_csv_and_markdown_from_same_cases(self) -> None: + cases = build_theory_cases() + + with tempfile.TemporaryDirectory() as temp_dir: + output_dir = Path(temp_dir) + write_theory_artifacts( + cases, + build_canonical_live_cases(cases), + build_momentum_capability_bands(cases), + output_dir, + ) + + json_path = output_dir / "theory-matrix.json" + csv_path = output_dir / "theory-matrix.csv" + md_path = output_dir / "theory-matrix.md" + canonical_path = output_dir / "canonical-live-cases.json" + capability_path = output_dir / "momentum-capabilities.json" + capability_md_path = output_dir / "momentum-capabilities.md" + + self.assertTrue(json_path.exists()) + self.assertTrue(csv_path.exists()) + self.assertTrue(md_path.exists()) + self.assertTrue(canonical_path.exists()) + self.assertTrue(capability_path.exists()) + self.assertTrue(capability_md_path.exists()) + + exported_cases = json.loads(json_path.read_text()) + self.assertEqual(len(cases), len(exported_cases)) + self.assertIn("| family | subfamily | movement_mode |", md_path.read_text()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_pathing_capabilities.py b/tools/tests/test_pathing_capabilities.py new file mode 100644 index 0000000000..ae6d267759 --- /dev/null +++ b/tools/tests/test_pathing_capabilities.py @@ -0,0 +1,71 @@ +import subprocess +import unittest + +from tools.pathing_theory.capabilities import build_momentum_capability_bands +from tools.pathing_theory.simulator import build_theory_cases + + +class PathingCapabilityTests(unittest.TestCase): + def test_linear_descend_sprint_dy_minus2_compresses_into_mm_breakpoints(self) -> None: + bands = build_momentum_capability_bands(build_theory_cases()) + rows = [ + band + for band in bands + if band.family == "linear" + and band.subfamily == "descend" + and band.movement_mode == "sprint" + and band.delta_y == -2.0 + ] + + self.assertEqual( + [(band.min_mm, band.max_mm, band.max_reach) for band in rows], + [(0, 0, 4), (1, 12, 5)], + ) + + def test_linear_ascend_walk_dy_plus1_compresses_into_mm_breakpoints(self) -> None: + bands = build_momentum_capability_bands(build_theory_cases()) + rows = [ + band + for band in bands + if band.family == "linear" + and band.subfamily == "ascend" + and band.movement_mode == "walk" + and band.delta_y == 1.0 + ] + + self.assertEqual( + [(band.min_mm, band.max_mm, band.max_reach) for band in rows], + [(0, 0, 1), (1, 12, 2)], + ) + + def test_ceiling_sprint_height_2p5_has_late_mm_breakpoint(self) -> None: + bands = build_momentum_capability_bands(build_theory_cases()) + rows = [ + band + for band in bands + if band.family == "ceiling" + and band.subfamily == "headhitter" + and band.movement_mode == "sprint" + and band.ceiling_height == 2.5 + ] + + self.assertEqual( + [(band.min_mm, band.max_mm, band.max_reach) for band in rows], + [(0, 7, 2), (8, 12, 3)], + ) + + def test_sim_jump_reach_lists_momentum_capabilities(self) -> None: + result = subprocess.run( + ["python3", "tools/sim_jump_reach.py", "--list-capabilities"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("linear | descend | sprint | dy=-2.0 | mm=0..0 | max_gap=4", result.stdout) + self.assertIn("linear | ascend | walk | dy=1.0 | mm=1..12 | max_gap=2", result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_pathing_contract_report.py b/tools/tests/test_pathing_contract_report.py new file mode 100644 index 0000000000..50349851d9 --- /dev/null +++ b/tools/tests/test_pathing_contract_report.py @@ -0,0 +1,40 @@ +from pathlib import Path + +import pytest + +from tools.pathing_contract_report import MetricsReport, SegmentMetric, parse_metrics, validate_report + + +def test_parse_metrics_reads_route_and_segment_ticks(tmp_path: Path) -> None: + fixture = Path("tools/testdata/pathing-contract-report.sample.log") + log = tmp_path / "sample.log" + log.write_text(fixture.read_text(encoding="utf-8"), encoding="utf-8") + + report = parse_metrics(log.read_text(encoding="utf-8")) + + assert report.total_ticks == 70 + assert [segment.ticks for segment in report.segments] == [17, 16] + + +def test_validate_report_includes_route_and_segment_table_on_budget_failure() -> None: + report = MetricsReport( + segments=[SegmentMetric(index=0, move="Parkour", ticks=17)], + total_ticks=70, + replans=0, + planned=[], + ) + planner_contract = {"segments": []} + timing_budget = { + "expectedTotalTicks": 60, + "maxTotalTicks": 65, + "segments": [ + {"moveType": "Parkour", "expectedTicks": 15, "maxTicks": 16}, + ], + } + + with pytest.raises(SystemExit) as exc: + validate_report("sample-scenario", report, planner_contract, timing_budget) + + message = str(exc.value) + assert "Route sample-scenario: actual=70 expected=60 max=65" in message + assert "seg[0] move=Parkour actual=17 expected=15 max=16 delta=+2" in message diff --git a/tools/tests/test_pathing_live_scripts.py b/tools/tests/test_pathing_live_scripts.py new file mode 100644 index 0000000000..67d1c4b8bf --- /dev/null +++ b/tools/tests/test_pathing_live_scripts.py @@ -0,0 +1,196 @@ +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +class PathingLiveScriptTests(unittest.TestCase): + def test_prepare_offline_config_redirects_bare_output_name_into_spill_dir(self) -> None: + with tempfile.TemporaryDirectory() as tempdir: + temp_path = Path(tempdir) + template_ini = temp_path / "template.ini" + template_ini.write_text( + "\n".join( + [ + "[Main.General]", + 'Account = { Login = "OldBot", Password = "" }', + 'AccountType = "microsoft"', + "", + "[Main.Advanced]", + 'MinecraftVersion = "auto"', + "TerrainAndMovements = false", + "InventoryHandling = false", + "EntityHandling = false", + "AutoRespawn = false", + "", + ] + ), + encoding="utf-8", + ) + spill_dir = temp_path / ".tmp" / "mcc-config-spill" + + result = subprocess.run( + [ + "bash", + str(REPO_ROOT / ".skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh"), + str(template_ini), + "1.21.11", + "1.21.11", + "MCCBot1", + ], + check=False, + capture_output=True, + text=True, + cwd=temp_path, + env={ + **os.environ, + **{"MCC_CONFIG_SPILL_DIR": str(spill_dir)}, + }, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse((temp_path / "1.21.11").exists()) + redirected_path = spill_dir / "1.21.11.ini" + self.assertTrue(redirected_path.exists()) + self.assertEqual(Path(result.stdout.strip()), redirected_path) + + def test_prepare_offline_config_treats_existing_output_ini_as_output_not_template(self) -> None: + with tempfile.TemporaryDirectory() as tempdir: + temp_path = Path(tempdir) + output_ini = temp_path / "MinecraftClient.debug.ini" + output_ini.write_text( + "\n".join( + [ + "[Main.General]", + 'Account = { Login = "OldBot", Password = "" }', + 'AccountType = "microsoft"', + "", + "[Main.Advanced]", + 'MinecraftVersion = "auto"', + "TerrainAndMovements = false", + "InventoryHandling = false", + "EntityHandling = false", + "AutoRespawn = false", + "", + ] + ), + encoding="utf-8", + ) + + result = subprocess.run( + [ + "bash", + str(REPO_ROOT / ".skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh"), + str(output_ini), + "1.21.11", + "MCCBot1", + ], + check=False, + capture_output=True, + text=True, + cwd=temp_path, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse((temp_path / "1.21.11").exists()) + + content = output_ini.read_text(encoding="utf-8") + self.assertIn('Account = { Login = "MCCBot1", Password = "-" }', content) + self.assertIn('AccountType = "mojang"', content) + self.assertIn('MinecraftVersion = "1.21.11"', content) + + def test_shared_integration_scripts_do_not_stop_servers_in_cleanup(self) -> None: + scripts = [ + REPO_ROOT / ".skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh", + REPO_ROOT / ".skills/mcc-integration-testing/scripts/run_parallel_session_smoke_test.sh", + REPO_ROOT / ".skills/mcc-integration-testing/scripts/run_achievements_test.sh", + ] + + for script in scripts: + content = script.read_text(encoding="utf-8") + self.assertNotIn("mc-stop", content, script.name) + self.assertNotIn("wait_for_server_stop", content, script.name) + + def test_test_parkour_lists_all_families(self) -> None: + result = subprocess.run( + ["python3", "tools/test-parkour.py", "--list-cases"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("linear/flat", result.stdout) + self.assertIn("linear/ascend", result.stdout) + self.assertIn("linear/descend", result.stdout) + self.assertIn("neo/neo", result.stdout) + self.assertIn("ceiling/headhitter", result.stdout) + self.assertIn("sidewall/flat", result.stdout) + self.assertIn("sidewall/ascend", result.stdout) + self.assertIn("sidewall/descend", result.stdout) + + def test_test_parkour_linear_has_reject_at_max_plus_one(self) -> None: + result = subprocess.run( + ["python3", "tools/test-parkour.py", "--list-cases", "--filter", "linear"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("linear-flat-gap4", result.stdout) + self.assertIn("linear-flat-gap5", result.stdout) + self.assertIn("[PASS]", result.stdout) + self.assertIn("[REJECT]", result.stdout) + + def test_test_parkour_linear_marks_live_boundary_cases_as_pass(self) -> None: + result = subprocess.run( + ["python3", "tools/test-parkour.py", "--list-cases", "--filter", "linear"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("linear-flat-gap4", result.stdout) + self.assertIn("linear-ascend-gap1-dy+1", result.stdout) + self.assertIn("linear-descend-gap2-dy-2", result.stdout) + self.assertIn("linear-descend-gap3-dy-1", result.stdout) + self.assertIn("linear-flat-gap4 gap=4 [PASS]", result.stdout) + self.assertIn("linear-ascend-gap1-dy+1 gap=1 [PASS]", result.stdout) + self.assertIn("linear-descend-gap2-dy-2 gap=2 [PASS]", result.stdout) + self.assertIn("linear-descend-gap3-dy-1 gap=3 [PASS]", result.stdout) + + def test_test_parkour_neo_covers_wall_range(self) -> None: + result = subprocess.run( + ["python3", "tools/test-parkour.py", "--list-cases", "--filter", "neo"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + for w in range(1, 5): + self.assertIn(f"neo-neo-wall{w}", result.stdout) + self.assertNotIn("neo-neo-wall0", result.stdout) + self.assertNotIn("neo-neo-wall5", result.stdout) + self.assertIn("[REJECT]", result.stdout) + + def test_test_pathing_theory_neo_ceiling_lists_theory_cases(self) -> None: + result = subprocess.run( + ["bash", "tools/test-pathing-theory-neo-ceiling.sh", "--list-cases"], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("neo-neo-sprint-mm12-wall1", result.stdout) + self.assertIn("ceiling-headhitter-sprint-mm12-gap3-ceil2p5", result.stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_pathing_theory_matrix.py b/tools/tests/test_pathing_theory_matrix.py new file mode 100644 index 0000000000..00c90ae23e --- /dev/null +++ b/tools/tests/test_pathing_theory_matrix.py @@ -0,0 +1,126 @@ +import unittest +from pathlib import Path + +from tools.pathing_theory.primitives import ( + can_reach_gap, + can_reach_gap_with_side_wall, + get_landing, +) +from tools.pathing_theory.simulator import build_theory_cases + + +class PathingTheoryMatrixTests(unittest.TestCase): + def test_build_theory_cases_returns_first_wave_families(self) -> None: + cases = build_theory_cases() + families = {(case.family, case.subfamily) for case in cases} + + self.assertIn(("linear", "flat"), families) + self.assertIn(("linear", "ascend"), families) + self.assertIn(("linear", "descend"), families) + self.assertIn(("neo", "neo"), families) + self.assertIn(("ceiling", "headhitter"), families) + self.assertIn(("sidewall", "flat"), families) + self.assertIn(("sidewall", "ascend"), families) + self.assertIn(("sidewall", "descend"), families) + + linear_boundary = next( + case + for case in cases + if case.case_id == "linear-flat-sprint-mm12-gap5-dy0p0" + ) + self.assertFalse(linear_boundary.expected_reachable) + self.assertTrue( + linear_boundary.margin is None or linear_boundary.margin < 0.0 + ) + + def test_walk_momentum_does_not_turn_gap4_ascend_into_reachable(self) -> None: + ok, landing_x, needed_x = can_reach_gap( + gap_blocks=4, + dy=1.0, + sprint=False, + momentum_ticks=12, + ) + + self.assertFalse(ok) + self.assertTrue(landing_x is None or landing_x < needed_x) + + def test_walk_momentum_can_reach_gap2_ascend_with_edge_takeoff_support(self) -> None: + ok, landing_x, needed_x = can_reach_gap( + gap_blocks=2, + dy=1.0, + sprint=False, + momentum_ticks=12, + ) + + self.assertTrue(ok) + self.assertIsNotNone(landing_x) + self.assertGreaterEqual(landing_x, needed_x) + + def test_walk_gap3_ascend_does_not_snap_up_after_falling_below_platform(self) -> None: + landing = get_landing( + sprint=False, + target_y=1.0, + landing_x_start=3.5, + momentum_ticks=12, + ) + + self.assertIsNone(landing) + + def test_sprint_with_run_up_still_treats_flat_gap5_as_unreachable(self) -> None: + ok, landing_x, needed_x = can_reach_gap( + gap_blocks=5, + dy=0.0, + sprint=True, + momentum_ticks=12, + ) + + self.assertFalse(ok) + self.assertTrue(landing_x is None or landing_x < needed_x) + + def test_sidewall_wo0_margin_is_less_or_equal_to_linear(self) -> None: + """Side wall should never make a jump easier than the open-air linear case.""" + cases = build_theory_cases() + linear_by_key: dict[tuple, float | None] = {} + for c in cases: + if c.family == "linear": + key = (c.subfamily, c.movement_mode, c.momentum_ticks, + c.gap_blocks, c.delta_y) + linear_by_key[key] = c.margin + + for c in cases: + if c.family != "sidewall" or c.wall_offset != 0: + continue + key = (c.subfamily, c.movement_mode, c.momentum_ticks, + c.gap_blocks, c.delta_y) + lin_margin = linear_by_key.get(key) + if lin_margin is None or c.margin is None: + continue + self.assertLessEqual( + c.margin, lin_margin + 1e-9, + f"sidewall margin {c.margin} > linear margin {lin_margin} " + f"for {c.case_id}", + ) + + def test_sidewall_walk_descend_gap3_mm0_is_unreachable_wo0(self) -> None: + """The tight walk descend dy=-2 gap3 mm0 should flip to unreachable with wall.""" + ok, _, _ = can_reach_gap_with_side_wall( + gap_blocks=3, dy=-2.0, wall_offset=0, sprint=False, momentum_ticks=0, + ) + self.assertFalse(ok) + + def test_sidewall_sprint_flat_gap4_mm12_is_still_reachable_wo0(self) -> None: + """Sprint flat gap4 with plenty of margin should survive the wall penalty.""" + ok, landing_x, needed_x = can_reach_gap_with_side_wall( + gap_blocks=4, dy=0.0, wall_offset=0, sprint=True, momentum_ticks=12, + ) + self.assertTrue(ok) + self.assertIsNotNone(landing_x) + self.assertGreater(landing_x, needed_x) + + def test_theory_markdown_mentions_canonical_live_coverage(self) -> None: + markdown = Path("tools/pathing_data/theory-matrix.md").read_text(encoding="utf-8") + self.assertIn("Canonical live coverage", markdown) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_pathing_theory_report.py b/tools/tests/test_pathing_theory_report.py new file mode 100644 index 0000000000..b13d32db0a --- /dev/null +++ b/tools/tests/test_pathing_theory_report.py @@ -0,0 +1,60 @@ +import json +import tempfile +import unittest +from pathlib import Path + +from tools.pathing_theory.report import build_report, classify_live_result, summarize_results + + +class PathingTheoryReportTests(unittest.TestCase): + def test_classify_live_result_distinguishes_expected_pass_and_reject(self) -> None: + self.assertEqual(classify_live_result("pass", "pass"), "expected_pass/live_pass") + self.assertEqual(classify_live_result("pass", "fail"), "expected_pass/live_fail") + self.assertEqual(classify_live_result("reject", "reject"), "expected_reject/live_reject") + self.assertEqual(classify_live_result("reject", "pass"), "expected_reject/live_unexpected_pass") + + def test_summarize_results_counts_each_status(self) -> None: + rows = [ + {"case_id": "a", "expected_result": "pass", "live_result": "pass"}, + {"case_id": "b", "expected_result": "pass", "live_result": "fail"}, + {"case_id": "c", "expected_result": "reject", "live_result": "reject"}, + ] + + summary = summarize_results(rows) + + self.assertEqual(summary["expected_pass/live_pass"], 1) + self.assertEqual(summary["expected_pass/live_fail"], 1) + self.assertEqual(summary["expected_reject/live_reject"], 1) + + def test_build_report_keeps_case_traceability_fields(self) -> None: + manifest_rows = [ + { + "case_id": "linear-flat-sprint-mm12-gap5-dy0p0", + "bucket_id": "linear:flat:sprint:boundary", + "world_recipe_id": "linear-flat", + "expected_result": "pass", + } + ] + result_row = { + "case_id": "linear-flat-sprint-mm12-gap5-dy0p0", + "live_result": "pass", + "log_path": "/tmp/mcc-debug/mcc-debug.log", + } + + with tempfile.TemporaryDirectory() as temp_dir: + manifest_path = Path(temp_dir) / "manifest.json" + results_path = Path(temp_dir) / "results.jsonl" + manifest_path.write_text(json.dumps(manifest_rows), encoding="utf-8") + results_path.write_text(json.dumps(result_row) + "\n", encoding="utf-8") + + report = build_report(manifest_path, results_path) + + row = report["rows"][0] + self.assertEqual(row["bucket_id"], "linear:flat:sprint:boundary") + self.assertEqual(row["world_recipe_id"], "linear-flat") + self.assertEqual(row["log_path"], "/tmp/mcc-debug/mcc-debug.log") + self.assertEqual(row["classification"], "expected_pass/live_pass") + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_test_parkour_metrics.py b/tools/tests/test_test_parkour_metrics.py new file mode 100644 index 0000000000..4c57d407ad --- /dev/null +++ b/tools/tests/test_test_parkour_metrics.py @@ -0,0 +1,511 @@ +import importlib.util +import queue +import sys +import threading +import tempfile +import unittest +from unittest import mock +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +SCRIPT_PATH = REPO_ROOT / "tools" / "test-parkour.py" + +spec = importlib.util.spec_from_file_location("test_parkour_script", SCRIPT_PATH) +assert spec is not None +module = importlib.util.module_from_spec(spec) +assert spec.loader is not None +sys.modules[spec.name] = module +spec.loader.exec_module(module) + + +class ParkourMetricsTests(unittest.TestCase): + class FakeRcon: + def __init__(self, responses: list[str | Exception]) -> None: + self._responses = list(responses) + self.commands: list[str] = [] + + def command(self, cmd: str) -> str: + self.commands.append(cmd) + if not self._responses: + return "ok" + response = self._responses.pop(0) + if isinstance(response, Exception): + raise response + return response + + class FakeMccClient: + def __init__(self, logs: list[str]) -> None: + self._logs = logs + self._read_index = 0 + self.sent_commands: list[str] = [] + + def log_length(self) -> int: + return 0 + + def send(self, command: str) -> None: + self.sent_commands.append(command) + + def read_log_from(self, _offset: int) -> str: + if self._read_index < len(self._logs): + log = self._logs[self._read_index] + self._read_index += 1 + return log + return self._logs[-1] + + @staticmethod + def build_debug_state_log(x: float, y: float, z: float, *, on_ground: bool) -> str: + return "\n".join( + [ + "=== MCC Debug State ===", + f"Location {x:.2f}, {y:.2f}, {z:.2f}", + f"OnGround {'true' if on_ground else 'false'}", + ] + ) + + def test_build_worker_session_name_scopes_workers_to_a_specific_run(self) -> None: + self.assertEqual( + module.build_worker_session_name("20260417t1715z", 4), + "parkour-20260417t1715z-4", + ) + + def test_build_case_session_name_is_unique_per_case_for_same_worker(self) -> None: + first = module.build_case_session_name("20260417t1715z", 4, 1) + second = module.build_case_session_name("20260417t1715z", 4, 2) + + self.assertEqual(first, "parkour-20260417t1715z-4-c1") + self.assertEqual(second, "parkour-20260417t1715z-4-c2") + self.assertNotEqual(first, second) + + def test_build_case_username_is_unique_per_case_for_same_worker(self) -> None: + first = module.build_case_username("MCCBot", 4, 1) + second = module.build_case_username("MCCBot", 4, 2) + + self.assertEqual(first, "MCCBot4c1") + self.assertEqual(second, "MCCBot4c2") + self.assertNotEqual(first, second) + + def test_parse_live_metrics_collects_machine_readable_counts(self) -> None: + log = "\n".join( + [ + "[DEBUG] [PathMetric] routeStart segments=4", + "[DEBUG] [PathMetric] segmentStart index=0 total=4 move=Traverse transition=ContinueStraight", + "[DEBUG] [PathMetric] segmentComplete index=0 total=4 move=Traverse ticks=7 x=101.50 y=80.00 z=315.50", + "[DEBUG] [PathMetric] replanStart count=1 x=101.50 y=80.00 z=315.50", + "[DEBUG] [PathMetric] replanSuccess count=1 segments=4", + "[DEBUG] [PathMetric] routeComplete totalTicks=19 replans=1", + "[MCC] [PathMgr] Navigation complete!", + ] + ) + + metrics = module.parse_live_metrics(log) + + self.assertEqual(metrics.route_start_count, 1) + self.assertEqual(metrics.route_complete_count, 1) + self.assertEqual(metrics.navigation_complete_count, 1) + self.assertEqual(metrics.replan_count, 1) + self.assertEqual(metrics.final_metric_position, (101.5, 80.0, 315.5)) + + def test_classify_outcome_pass_requires_zero_replans_and_zero_turn_stalls(self) -> None: + metrics = module.LiveMetrics( + route_complete_count=1, + navigation_complete_count=1, + replan_count=0, + turn_stall_count=0, + ) + + self.assertEqual(module.classify_outcome(metrics, near_goal=True), "pass") + self.assertEqual(module.classify_outcome(metrics, near_goal=False), "fail") + + def test_classify_outcome_replan_is_fail(self) -> None: + metrics = module.LiveMetrics( + route_complete_count=1, + navigation_complete_count=1, + replan_count=1, + turn_stall_count=0, + ) + + self.assertEqual(module.classify_outcome(metrics, near_goal=True), "fail") + + def test_classify_outcome_turn_stall_is_fail(self) -> None: + metrics = module.LiveMetrics( + route_complete_count=1, + navigation_complete_count=1, + replan_count=0, + turn_stall_count=1, + ) + + self.assertEqual(module.classify_outcome(metrics, near_goal=True), "fail") + + def test_classify_outcome_prefers_harness_error(self) -> None: + metrics = module.LiveMetrics(route_complete_count=1, navigation_complete_count=1) + + self.assertEqual( + module.classify_outcome(metrics, near_goal=True, error_kind="harness_rcon_unavailable"), + "harness_rcon_unavailable", + ) + + def test_classify_outcome_planner_reject_stays_reject(self) -> None: + metrics = module.LiveMetrics(planner_reject_count=1) + + self.assertEqual(module.classify_outcome(metrics, near_goal=False), "reject") + + def test_classify_outcome_a_star_failed_without_route_start_is_reject(self) -> None: + log = "\n".join( + [ + "[DEBUG] [A*] Start (100,80,324), goal=GoalBlock(121, 80, 324)", + "[DEBUG] [A*] Failed, 11986 nodes, 1301ms", + "[MCC] [Navigate] A* result: Failed, nodes=11986, time=1301ms, path length=0", + "[MCC] [FileInput] No path found (11986 nodes explored in 1301ms)", + ] + ) + + metrics = module.parse_live_metrics(log) + + self.assertEqual(module.classify_outcome(metrics, near_goal=False), "reject") + + def test_count_turn_stalls_requires_large_yaw_swings_with_low_motion(self) -> None: + samples = [ + module.NavigationSample(x=100.5, y=80.0, z=100.5, yaw=0.0), + module.NavigationSample(x=100.55, y=80.0, z=100.5, yaw=70.0), + module.NavigationSample(x=100.58, y=80.0, z=100.5, yaw=150.0), + module.NavigationSample(x=100.60, y=80.0, z=100.5, yaw=235.0), + ] + + self.assertEqual(module.count_turn_stalls(samples), 1) + + def test_parse_entity_fields_reads_position_and_rotation(self) -> None: + pos = module.parse_entity_position("[1.5d, 80.0d, -12.25d]") + yaw_pitch = module.parse_entity_rotation("[90.0f, 15.0f]") + + self.assertEqual(pos, (1.5, 80.0, -12.25)) + self.assertEqual(yaw_pitch, (90.0, 15.0)) + + def test_parse_debug_state_location_reads_latest_location(self) -> None: + log = "\n".join( + [ + "=== MCC Debug State ===", + "Location 100.50, 80.00, 216.50", + "=== MCC Debug State ===", + "Location 112.25, 79.00, 297.50", + ] + ) + + self.assertEqual( + module.parse_debug_state_location(log), + (112.25, 79.0, 297.5), + ) + + def test_parse_debug_state_snapshot_reads_latest_location_and_on_ground(self) -> None: + log = "\n".join( + [ + "=== MCC Debug State ===", + "Location 100.50, 80.00, 216.50", + "OnGround true", + "=== MCC Debug State ===", + "Location 112.25, 79.00, 297.50", + "OnGround false", + ] + ) + + snapshot = module.parse_debug_state_snapshot(log) + + self.assertIsNotNone(snapshot) + assert snapshot is not None + self.assertEqual(snapshot.location, (112.25, 79.0, 297.5)) + self.assertFalse(snapshot.on_ground) + + def test_is_near_expected_position_requires_tight_xyz_tolerance(self) -> None: + self.assertTrue( + module.is_near_expected_position( + (100.5, 80.0, 216.5), + (100.5, 80.0, 216.5), + ) + ) + self.assertFalse( + module.is_near_expected_position( + (100.5, 79.45, 216.5), + (100.5, 80.0, 216.5), + ) + ) + self.assertFalse( + module.is_near_expected_position( + (100.72, 80.0, 216.5), + (100.5, 80.0, 216.5), + ) + ) + + def test_wait_for_local_start_sync_requires_local_on_ground(self) -> None: + expected = (100.5, 80.0, 225.5) + client = self.FakeMccClient( + [ + self.build_debug_state_log(*expected, on_ground=False), + self.build_debug_state_log(*expected, on_ground=False), + self.build_debug_state_log(*expected, on_ground=False), + self.build_debug_state_log(*expected, on_ground=True), + self.build_debug_state_log(*expected, on_ground=True), + self.build_debug_state_log(*expected, on_ground=True), + ] + ) + + clock_ticks = [0] + + def fake_monotonic() -> float: + clock_ticks[0] += 1 + return clock_ticks[0] * 0.1 + + with mock.patch.object(module.time, "sleep", lambda _seconds: None): + with mock.patch.object(module.time, "monotonic", side_effect=fake_monotonic): + synced = module.wait_for_local_start_sync( + client, + expected, + timeout_seconds=2.0, + stable_reads_required=3, + ) + + self.assertTrue(synced) + self.assertEqual(client.sent_commands, ["debug state"] * 6) + + def test_wait_for_rcon_ready_retries_until_list_succeeds(self) -> None: + rcon = self.FakeRcon( + [ + ConnectionRefusedError("not ready"), + TimeoutError("still not ready"), + "There are 0 of a max of 20 players online", + ] + ) + + clock_ticks = [0] + + def fake_monotonic() -> float: + clock_ticks[0] += 1 + return clock_ticks[0] * 0.1 + + with mock.patch.object(module.time, "sleep", lambda _seconds: None): + with mock.patch.object(module.time, "monotonic", side_effect=fake_monotonic): + ready = module.wait_for_rcon_ready( + rcon, + timeout_seconds=1.0, + poll_interval=0.1, + ) + + self.assertTrue(ready) + self.assertEqual(rcon.commands, ["list", "list", "list"]) + + def test_connect_rcon_with_retry_retries_initial_connect(self) -> None: + events: list[str] = [] + attempts = {"count": 0} + + class FakeClient: + def connect(self) -> None: + attempts["count"] += 1 + events.append(f"connect-{attempts['count']}") + if attempts["count"] < 3: + raise ConnectionRefusedError("server not ready") + + def fake_factory(*_args, **_kwargs) -> FakeClient: + return FakeClient() + + clock_ticks = [0] + + def fake_monotonic() -> float: + clock_ticks[0] += 1 + return clock_ticks[0] * 0.1 + + with mock.patch.object(module.time, "sleep", lambda _seconds: None): + with mock.patch.object(module.time, "monotonic", side_effect=fake_monotonic): + client = module.connect_rcon_with_retry( + host="localhost", + port=25575, + password="test123", + timeout_seconds=1.0, + poll_interval=0.1, + client_factory=fake_factory, + ) + + self.assertIsNotNone(client) + self.assertEqual(events, ["connect-1", "connect-2", "connect-3"]) + + def test_make_skip_result_records_skip_reason(self) -> None: + case = module.TestCase( + case_id="linear-flat-gap4", + family="linear", + subfamily="flat", + gap_or_wall=4, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + + result = module.make_skip_result(case, "group_failed_earlier") + + self.assertEqual(result.outcome, "skipped") + self.assertEqual(result.skip_reason, "group_failed_earlier") + self.assertEqual(result.error_kind, None) + self.assertFalse(result.matched_expected) + + def test_result_to_record_includes_session_paths_and_duration(self) -> None: + case = module.TestCase( + case_id="linear-flat-gap1", + family="linear", + subfamily="flat", + gap_or_wall=1, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + result = module.TestResult( + case=case, + outcome="pass", + matched_expected=True, + replan_count=0, + turn_stall_count=0, + near_goal=True, + final_position=(109.5, 80.0, 200.5), + total_ticks=42, + session="parkour-run123-2", + log_path="/tmp/parkour-runs/run123/workers/2/worker.log", + event_log_path="/tmp/parkour-runs/run123/events.jsonl", + duration_ms=4200, + error_kind=None, + skip_reason=None, + ) + + record = module.result_to_record(result, worker_id=2) + + self.assertEqual(record["worker"], 2) + self.assertEqual(record["session"], "parkour-run123-2") + self.assertEqual(record["log_path"], "/tmp/parkour-runs/run123/workers/2/worker.log") + self.assertEqual(record["event_log_path"], "/tmp/parkour-runs/run123/events.jsonl") + self.assertEqual(record["duration_ms"], 4200) + self.assertEqual(record["skip_reason"], None) + self.assertEqual(record["error_kind"], None) + + def test_summarize_results_groups_outcomes_by_family(self) -> None: + summary = module.summarize_results( + [ + {"family": "linear", "outcome": "pass", "matched": True}, + {"family": "linear", "outcome": "reject", "matched": True}, + {"family": "linear", "outcome": "skipped", "matched": False}, + {"family": "neo", "outcome": "reject", "matched": False}, + ] + ) + + self.assertEqual(summary["total"], 4) + self.assertEqual(summary["matched"], 2) + self.assertEqual(summary["families"]["linear"]["outcomes"]["pass"], 1) + self.assertEqual(summary["families"]["linear"]["outcomes"]["skipped"], 1) + self.assertEqual(summary["families"]["neo"]["mismatches"], 1) + + def test_write_summary_files_persists_json_and_markdown(self) -> None: + summary = { + "total": 4, + "matched": 2, + "mismatched": 2, + "families": { + "linear": { + "total": 3, + "matched": 2, + "mismatches": 1, + "outcomes": {"pass": 1, "reject": 1, "skipped": 1}, + } + }, + } + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir) + module.write_summary_files(run_dir, summary) + + summary_json = run_dir / "summary.json" + summary_md = run_dir / "summary.md" + + self.assertTrue(summary_json.exists()) + self.assertTrue(summary_md.exists()) + self.assertIn('"total": 4', summary_json.read_text(encoding="utf-8")) + self.assertIn("linear", summary_md.read_text(encoding="utf-8")) + + def test_append_jsonl_record_writes_to_all_requested_paths(self) -> None: + record = {"case_id": "linear-flat-gap1", "outcome": "pass"} + + with tempfile.TemporaryDirectory() as tempdir: + base = Path(tempdir) + path1 = base / "results-a.jsonl" + path2 = base / "results-b.jsonl" + + module.append_jsonl_record([path1, path2], record) + + self.assertEqual(path1.read_text(encoding="utf-8"), path2.read_text(encoding="utf-8")) + self.assertIn('"case_id": "linear-flat-gap1"', path1.read_text(encoding="utf-8")) + + def test_worker_loop_reuses_one_worker_context_for_multiple_cases(self) -> None: + case1 = module.TestCase( + case_id="linear-flat-gap1", + family="linear", + subfamily="flat", + gap_or_wall=1, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + case2 = module.TestCase( + case_id="linear-flat-gap2", + family="linear", + subfamily="flat", + gap_or_wall=2, + delta_y=0.0, + ceiling_height=None, + wall_offset=None, + expected="pass", + ) + layout1 = module.CourseLayout(100, 80, 200, 109, 80, 200, (0, 0, 0), (0, 0, 0)) + layout2 = module.CourseLayout(100, 80, 210, 112, 80, 210, (0, 0, 0), (0, 0, 0)) + group_q: queue.Queue = queue.Queue() + group_q.put((case1.group_key(), [(case1, layout1)])) + group_q.put((case2.group_key(), [(case2, layout2)])) + + fake_ctx = module.WorkerContext( + worker_id=1, + username="MCCBot1", + session="parkour-run123-1", + rcon=mock.Mock(), + mcc=mock.Mock(), + ) + all_results: list[module.TestResult] = [] + skipped_counter = [0] + + def make_result(case: module.TestCase, *_args, **_kwargs) -> module.TestResult: + return module.TestResult(case=case, outcome="pass", matched_expected=True) + + with mock.patch.object(module, "launch_worker_context", return_value=fake_ctx) as launch_mock: + with mock.patch.object(module, "reset_worker_state", create=True, return_value=True) as reset_mock: + with mock.patch.object(module, "run_single_test", side_effect=make_result) as run_mock: + with mock.patch.object(module, "cleanup_workers") as cleanup_mock: + module.worker_loop( + worker_id=1, + base_username="MCCBot", + run_token="run123", + version="1.21.11-Vanilla", + server_port=25565, + rcon_port=25575, + rcon_password="test123", + group_queue=group_q, + all_results=all_results, + results_lock=threading.Lock(), + wait_seconds=15, + results_paths=[], + workers_registry=[], + registry_lock=threading.Lock(), + skipped_counter=skipped_counter, + ) + + self.assertEqual(launch_mock.call_count, 1) + self.assertEqual(reset_mock.call_count, 2) + self.assertEqual(run_mock.call_count, 2) + cleanup_mock.assert_not_called() + self.assertEqual(len(all_results), 2) + + +if __name__ == "__main__": + unittest.main()