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 00000000..5548736e
--- /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 00000000..c853eb9f
--- /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 CursorBot
+# 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
+```
+
+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-template-convergence.md b/docs/superpowers/plans/2026-04-12-pathing-template-convergence.md
new file mode 100644
index 00000000..4443aecf
--- /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}"
+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
+```
+
+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` 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 00000000..488c81b7
--- /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"
+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' CursorBot - 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 CursorBot 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 CursorBot 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/specs/2026-04-12-parkour-admissibility-design.md b/docs/superpowers/specs/2026-04-12-parkour-admissibility-design.md
new file mode 100644
index 00000000..d92dae29
--- /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.