pathing: sidewall runup precondition via EntryPreparation

Introduce an EntryPreparationState carried on PathNode + A* context so
sidewall parkour can explicitly request one or more runway traverses
before takeoff instead of silently dropping the move. ParkourFeasibility
gains TryGetRequiredStaticEntryRunupSteps + HasPreparedRunup helpers so
long descends (major=5, dy=-1) only remain feasible when the preceding
node proved the runup.

Widen HasDominantAxisRunUp to accept cold-start sprint-jumps within
~3.1-3.5 blocks horizontally so lone overhang / staircase takeoffs stay
feasible without a 2-block runway (matches Baritone's MomentumBehavior
.ALLOWED contract).

Add a runtime SidewallParkourController that implements the corner
commitment + wall-hug chain during execution.

Extend pathing test fixtures with InitialMomentumTicks, add sidewall
accepted/rejected scenarios, and refresh timing + contract baselines to
reflect the new planner shapes. Document the design in
docs/superpowers/specs and plans.

Made-with: Cursor
This commit is contained in:
BruceChen 2026-04-19 17:03:03 +00:00
parent 95b20d9d1c
commit da52aa5c3c
15 changed files with 2925 additions and 25 deletions

View file

@ -146,6 +146,27 @@ public sealed class LivePathingRegressionTests
segment => Assert.Equal(ParkourProfile.Default, segment.ParkourProfile));
}
[Fact]
public void AStar_LinearFlatGap4_DoesNotInsertRunupSetupSegments()
{
PathingExecutionScenario scenario = LinearParkourScenarioBuilder.Create("linear-flat-gap4", gap: 4, deltaY: 0);
PathResult result = PathingScenarioRunner.PlanOnly(scenario);
List<PathSegment> segments = PathSegmentBuilder.FromPath(result.Path);
Assert.Equal(PathStatus.Success, result.Status);
int firstParkourIndex = segments.FindIndex(segment => segment.MoveType == MoveType.Parkour);
Assert.Equal(3, firstParkourIndex);
Assert.All(
segments.Take(firstParkourIndex),
segment =>
{
Assert.Equal(MoveType.Traverse, segment.MoveType);
Assert.True(segment.End.X > segment.Start.X, segment.ToString());
});
Assert.Equal(new Location(3.5, 80, 0.5), segments[firstParkourIndex - 1].End);
}
[Theory]
[InlineData("linear-ascend-gap2-dy+1", 2, 1)]
[InlineData("linear-descend-gap4-dy-1", 4, -1)]
@ -225,6 +246,30 @@ public sealed class LivePathingRegressionTests
Assert.Equal(scenario.Goal.Z + 0.5, segments[^1].End.Z);
}
[Theory]
[InlineData("sidewall-descend-gap5-dy-1-wo0", 5, 0)]
[InlineData("sidewall-descend-gap5-dy-1-wo1", 5, 1)]
public void AStar_SidewallLongDescendStaticEntry_PrependsExplicitRunupTraverses(string scenarioId, int gap, int wallOffset)
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(
scenarioId,
gap,
deltaY: -1,
wallOffset);
PathResult result = PathingScenarioRunner.PlanOnly(scenario);
List<PathSegment> segments = PathSegmentBuilder.FromPath(result.Path);
Assert.Equal(PathStatus.Success, result.Status);
int firstParkourIndex = segments.FindIndex(segment => segment.MoveType == MoveType.Parkour);
Assert.True(firstParkourIndex >= 2, string.Join('\n', segments));
Assert.All(
segments.Take(firstParkourIndex),
segment => Assert.Equal(MoveType.Traverse, segment.MoveType));
Assert.Equal(new Location(100.5, 80, 100.5), segments[firstParkourIndex - 1].End);
Assert.Equal(ParkourProfile.Sidewall, segments[firstParkourIndex].ParkourProfile);
}
[Theory]
[MemberData(nameof(SidewallParkourScenarioBuilder.RejectedCases), MemberType = typeof(SidewallParkourScenarioBuilder))]
public void AStar_SidewallRejectedCases_RejectBeforeExecution(string scenarioId, int gap, int deltaY, int wallOffset)

View file

@ -225,6 +225,22 @@ public sealed class PathSegmentManagerTests
$"replanCount={manager.ReplanCount}\ninfo={string.Join('\n', infoLogs)}\ndebug={string.Join('\n', debugLogs)}");
}
[Theory]
[MemberData(nameof(SidewallParkourScenarioBuilder.AcceptedCases), MemberType = typeof(SidewallParkourScenarioBuilder))]
public void Tick_SidewallAcceptedCases_CompletesWithoutReplan(string scenarioId, int gap, int deltaY, int wallOffset)
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create(scenarioId, gap, deltaY, wallOffset);
PathingScenarioResult result = PathingScenarioRunner.RunAccepted(scenario);
Location goalLocation = new(scenario.Goal.X + 0.5, scenario.Goal.Y, scenario.Goal.Z + 0.5);
Assert.True(
result.Completed
&& result.ReplanCount == 0
&& TemplateFootingHelper.IsFootprintInsideTargetBlock(result.FinalPosition, goalLocation),
$"scenario={scenarioId} completed={result.Completed} replans={result.ReplanCount} final={result.FinalPosition} " +
$"goal={goalLocation} planStatus={result.PlanResult.Status}\ninfo={string.Join('\n', result.InfoLogs)}\ndebug={string.Join('\n', result.DebugLogs)}");
}
[Theory]
[MemberData(nameof(LinearParkourScenarioBuilder.AcceptedCases), MemberType = typeof(LinearParkourScenarioBuilder))]
public void Tick_LinearAcceptedChain_CompletesWithoutReplan(string scenarioId, int gap, int deltaY)

View file

@ -10,5 +10,6 @@ internal sealed record PathingExecutionScenario
public required Location Start { get; init; }
public required GoalBlock Goal { get; init; }
public required float StartYaw { get; init; }
public int InitialMomentumTicks { get; init; }
public required int MaxExecutionTicks { get; init; }
}

View file

@ -640,7 +640,278 @@ public sealed class SprintJumpTemplateScenarioTests
Assert.True(input.Jump);
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap2_FinalStop_CompletesInsideLandingBlock()
{
World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0);
var segment = new PathSegment
{
Start = new Location(100.5, 80, 100.5),
End = new Location(99.5, 80, 102.5),
MoveType = MoveType.Parkour,
ParkourProfile = ParkourProfile.Sidewall,
ExitTransition = PathTransitionType.FinalStop
};
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f);
PathSegment[] segments = [segment];
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(
state == TemplateState.Complete,
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segment}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segment}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap2_SecondPrepareJump_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap2-wo0", gap: 2, deltaY: 0, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 0);
TemplateState state = RunSegment(segments, index: 1, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[1].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap3Wo1_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap3-wo1", gap: 3, deltaY: 0, wallOffset: 1);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap4Wo0_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap4-wo0", gap: 4, deltaY: 0, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap3Wo1_SecondPrepareJump_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap3-wo1", gap: 3, deltaY: 0, wallOffset: 1);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 0);
TemplateState state = RunSegment(segments, index: 1, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[1].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[1]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallAscendGap3Wo1_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-ascend-gap3-dy+1-wo1", gap: 3, deltaY: 1, wallOffset: 1);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap2DyMinus1Wo0_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-1-wo0", gap: 2, deltaY: -1, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap2DyMinus2Wo0_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-2-wo0", gap: 2, deltaY: -2, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap2DyMinus1Wo0_FinalStop_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-1-wo0", gap: 2, deltaY: -1, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap2DyMinus2Wo0_FinalStop_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap2-dy-2-wo0", gap: 2, deltaY: -2, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap5DyMinus1Wo0_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-1-wo0", gap: 5, deltaY: -1, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap5DyMinus1Wo1_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-1-wo1", gap: 5, deltaY: -1, wallOffset: 1);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap5DyMinus2Wo0_FirstPrepareJump_CompletesFromStart()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-2-wo0", gap: 5, deltaY: -2, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
TemplateState state = RunSegment(segments, index: 0, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[0].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[0]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap5DyMinus2Wo0_FinalStop_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap5-dy-2-wo0", gap: 5, deltaY: -2, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap3DyMinus1Wo0_FinalStop_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap3-dy-1-wo0", gap: 3, deltaY: -1, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallFlatGap2_FinalStop_CompletesFromChainCarry()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-flat-gap2-wo0", gap: 2, deltaY: 0, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
[Fact]
public void SprintJumpTemplate_SidewallDescendGap4DyMinus1Wo0_FinalStop_CompletesInsideLandingBlock()
{
PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create("sidewall-descend-gap4-dy-1-wo0", gap: 4, deltaY: -1, wallOffset: 0);
(World world, List<PathSegment> segments, PlayerPhysics physics) = BuildPlannedScenario(scenario);
RunSegmentsThrough(segments, world, physics, lastCompletedIndex: 1);
TemplateState state = RunSegment(segments, index: 2, physics, world, out Location finalPos, out string trace);
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
Assert.True(
TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segments[2].End),
$"state={state} finalPos={finalPos} vel={physics.DeltaMovement} segment={segments[2]}\n{trace}");
}
private static (World World, List<PathSegment> Segments, PlayerPhysics Physics) BuildPlannedLinearScenario(PathingExecutionScenario scenario)
{
return BuildPlannedScenario(scenario);
}
private static (World World, List<PathSegment> Segments, PlayerPhysics Physics) BuildPlannedScenario(PathingExecutionScenario scenario)
{
PathResult planResult = PathingScenarioRunner.PlanOnly(scenario);
@ -685,7 +956,7 @@ public sealed class SprintJumpTemplateScenarioTests
state = template.Tick(pos, physics, input, world);
tail.Enqueue(
$"tick={tick} state={state} pos={pos} vel={physics.DeltaMovement} yaw={physics.Yaw:F1} onGround={physics.OnGround} " +
$"input(F={input.Forward},B={input.Back},J={input.Jump},S={input.Sprint})");
$"input(F={input.Forward},B={input.Back},L={input.Left},R={input.Right},J={input.Jump},S={input.Sprint})");
if (tail.Count > 40)
tail.Dequeue();