Minecraft-Console-Client/MinecraftClient/Pathing/Moves/IMoveExpander.cs
BruceChen d002930a6a pathing: unify jump moves into MoveJump + IMoveExpander
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
2026-04-19 17:03:26 +00:00

53 lines
1.8 KiB
C#

using System;
using MinecraftClient.Pathing.Core;
namespace MinecraftClient.Pathing.Moves;
/// <summary>
/// A feasible neighbor emitted by an <see cref="IMoveExpander"/>. Carries the
/// per-node data the A* main loop needs to update its open set (destination,
/// cost, parkour profile, move type).
/// </summary>
public readonly struct MoveNeighbor
{
public readonly int DestX;
public readonly int DestY;
public readonly int DestZ;
public readonly double Cost;
public readonly ParkourProfile ParkourProfile;
public readonly MoveType MoveType;
public MoveNeighbor(in MoveResult result, MoveType moveType)
{
DestX = result.DestX;
DestY = result.DestY;
DestZ = result.DestZ;
Cost = result.Cost;
ParkourProfile = result.ParkourProfile;
MoveType = moveType;
}
}
/// <summary>
/// Emits feasible neighbors from a given node. Replaces the old
/// "iterate every pre-instantiated IMove" pattern: A* asks each expander to
/// fill a stack-allocated buffer with all feasible neighbors, and the
/// expander can prune whole categories (e.g. skip Sidewall when no wall is
/// near) without instantiating per-direction IMove objects.
/// </summary>
public interface IMoveExpander
{
/// <summary>
/// Probe the world and populate <paramref name="buffer"/> with feasible
/// neighbors. Returns the number of neighbors written. Implementations
/// must not write past the buffer; callers must size it to the expander's
/// max output.
/// </summary>
int Expand(CalculationContext ctx, int x, int y, int z, Span<MoveNeighbor> buffer);
/// <summary>
/// Upper bound on the number of neighbors this expander can emit from a
/// single node. Used by the driver to size the per-node buffer.
/// </summary>
int MaxNeighbors { get; }
}