Replace seven hand-written IMove classes (MoveTraverse, MoveDiagonal,
MoveAscend, MoveDiagonalAscend, MoveDiagonalDescend, MoveParkour,
MoveSidewallParkour) with a single MoveJump driven by a JumpDescriptor
(XOffset, ZOffset, YDelta, JumpFlavor). JumpFeasibility is the single
source of truth for the physics/cost rules of every jump-family move.
A* no longer iterates a flat IMove[]. The Calculate loop now drives
an IMoveExpander[] that writes into a stackalloc Span<MoveNeighbor>,
eliminating per-iteration heap traffic. JumpExpander enumerates every
jump-family descriptor dynamically; LegacyMoveExpander wraps the
remaining dynamic-landing moves (MoveDescend, MoveSprintDescend,
MoveClimb, MoveFall) so callers that still pass a custom IMove[]
keep working.
Add two O(1) short-circuits at the top of JumpExpander.Expand:
- Hoist the per-node parkour preconditions (AllowParkour + CanSprint,
standing block climbability, feet-liquid, head clearance at y+2)
so ~170 SprintJump + Sidewall descriptors never call JumpFeasibility
when the node cannot take off at all.
- Precompute an 8-way "first step has no floor" table indexed by
(sign(dx), sign(dz)) so SprintJump descriptors in a direction that
has a walkable floor underneath are dropped without Evaluate.
- Add a conservative "any cardinal wall at y or y+1" probe that skips
all 112 Sidewall descriptors when no wall exists adjacent to the
takeoff.
Move tests switch to the new MoveJump.* factory methods. Behavior is
verified by the existing test suite: the 21 pre-existing baseline
failures are preserved exactly, 0 regressions introduced.
Made-with: Cursor