Skip to content

Planning Without the Follower

Alexander Parent edited this page Jul 31, 2026 · 1 revision

Planning Without the Follower

To feed your own movement system instead of the built-in follower.

PathResult path = new DiscreteGroundPathfinder().findPath(
        instance,
        entity.getPosition(),
        destination,
        entity.getBoundingBox(),
        MobTraversalProfile.DEFAULT,
        GroundSearchLimits.ofPathLength(64),
        GroundCapabilities.STANDARD,
        SearchControl.NONE);

if (path.found()) {
    for (PathNode node : path.nodes()) feed(node.asVec());
}

PathNode positions are the entity bottom-center; the first is the snapped start. An unreachable goal returns PARTIAL with the best route found.

Entity-aware searching across every navigation mode uses EntityPathfinder with a NavigationRequest. Several destinations can share one frontier and one budget:

PathResult result = new EntityPathfinder()
        .findPathToAny(request, candidates, SearchControl.NONE);

Reached alternatives rank by node count; if none is reached, the partial path ranks by remaining Manhattan distance, then node count.

Why a search stopped

PathStatus says what a search produced. PathResult.stop() says what ended it, which is what decides whether retrying is worth anything.

PathStop Meaning
REACHED reached; exactly the FOUND results
FRONTIER_EXHAUSTED open set emptied with nothing withheld; unreachable from the start
FRONTIER_TRUNCATED a budget, deadline or cancellation ended it with cells still open
LENGTH_BOUNDED open set emptied only because maxPathLength withheld cells
NOT_SEARCHED shed, invalid, or replayed from a cache

Only FRONTIER_EXHAUSTED describes the world; the others describe your budget.

if (!result.found() && result.stop() == PathStop.FRONTIER_EXHAUSTED) {
    chooseAnotherGoal();          // no budget will ever reach this one
}

The controller republishes this as routeStop() and targetUnreachable().

Reusable plans

NavigationPlan is immutable and does not retain the world, so cache it against a revision you own and replay it without searching again.

NavigationPlan plan = navigation.plan(request).join();
NavigationPlanCache<String> routes = new NavigationPlanCache<>(256);
routes.put("guard:a-to-b", worldRevision, plan);

routes.get("guard:a-to-b", worldRevision, guard.getPosition(), 0.5)
        .ifPresent(controller::follow);

Increment the revision after relevant block changes. Store A-to-B and B-to-A separately: steps, falls, jumps, doors and costs are not symmetric.

Clone this wiki locally