Skip to content

Wall Crawling

AzureDoom edited this page Aug 29, 2026 · 1 revision

Wall Crawling

AzureCortex includes an optional movement model for mobs that crawl along walls, ceilings, and floors, the generalization of the historical Ovomorphosis xenomorph movement. This is entirely opt-in: implement CrawlCapability on your entity and the rest of the framework's navigation/pathfinding code will route through the crawl-aware evaluators; entities that don't implement it are unaffected.

CrawlCapability, the data contract

A mixin-style interface your entity class implements (typically via a mixin onto your entity base class):

public interface CrawlCapability {
    boolean isWallCrawling();
    void setWallCrawling(boolean crawling);
    int getWallCrawlGraceTicks();
    void setWallCrawlGraceTicks(int ticks);
    Vec3 getCrawlForward();
    Vec3 getOldCrawlForward();
    Vec3 getCrawlUp();
    Vec3 getOldCrawlUp();
    double getCrawlDistFromBlock();
    double getOldCrawlDistFromBlock();
    void setCrawlOrientation(Vec3 forward, Vec3 up, double distFromBlock);
}

The old* accessors exist for smooth render-frame interpolation between the previous and current tick's orientation.

Reference implementation: CrawlState

If you don't want to hand-roll storage, CrawlState implements the bookkeeping backed by synced entity data (EntityDataAccessors), you still register the SynchedEntityData.defineId fields yourself and typically delegate your CrawlCapability methods to a held CrawlState instance:

private final CrawlState crawlState = new CrawlState(
    this, IS_CRAWLING, CRAWL_FWD_X, CRAWL_FWD_Y, CRAWL_FWD_Z,
    CRAWL_UP_X, CRAWL_UP_Y, CRAWL_UP_Z, CRAWL_DIST
);

// then, e.g.:
@Override public boolean isWallCrawling() { return crawlState.isWallCrawling(); }

Call crawlState.tick() once per tick from your entity's own tick method to keep interpolation state and grace ticks current.

CrawlController, physics and orientation

Drives gravity suppression, surface alignment, and (via NavigationHandler) movement for crawl-capable mobs. Call these each tick from your entity's tick method:

CrawlController.updateWallCrawlingPhysics(mob); // gravity suppression, grace-tick countdown
CrawlController.updateCrawlOrientation(mob, movement); // surface alignment

Useful static queries:

CrawlController.canWallCrawl(mob);            // implements CrawlCapability, not in water/vehicle
CrawlController.isWallCrawling(mob);
CrawlController.wasRecentlyWallCrawling(mob);  // currently crawling OR still has grace ticks left
CrawlController.setWallCrawling(mob, true);

wasRecentlyWallCrawling matters when one action takes over from a crawl-driven approach action, it lets the new action inherit "was crawling" state even after the previous action already cleared the flag.

CrawlController implements NavigationHandler and delegates computeMovement to MovementController, see Navigation and Pathfinding, so wall-crawl-capable mobs still get the same obstacle-steering/danger-repulsion behavior on top of crawl physics.

CrawlTraversalEvaluator, crawl-aware pathfinding

The crawl-aware A* pathfinder: understands tight-tunnel crawling, vertical-shaft climbing, and surface-cling climb nodes reached via CrawlCapability, on top of everything AStarPathfinder already does. Routes hazard/passable-solid classification through MovementCapability (see Navigation and Pathfinding) exactly like the plain pathfinder — nothing about "which blocks are climbable/passable for this mob" is hard-coded.

var path = CrawlTraversalEvaluator.INSTANCE.findPath(mob, start, goal, 96, 1);

// or with a caller-supplied cache, e.g. inside a larger per-tick scope:
var path = CrawlTraversalEvaluator.INSTANCE.findPath(mob, start, goal, 96, 1, cache);

Searches up to 6,000 nodes (vs. 2,000 for the plain pathfinder) since crawl-capable creatures typically need to explore a larger, more three-dimensional search space. Use IncrementalPathSession.crawling(...) (see Navigation and Pathfinding) to spread that cost across ticks rather than paying for it synchronously.

Putting it together

A crawl-capable entity typically:

  1. Implements CrawlCapability (directly or via a mixin delegating to CrawlState).
  2. Calls CrawlController.updateWallCrawlingPhysics/updateCrawlOrientation each tick.
  3. Uses CrawlTraversalEvaluator.INSTANCE (or an IncrementalPathSession.crawling(...) session) as its Pathfinder in movement actions like MoveToDestinationAction (see Built-in Actions).
  4. Uses CrawlController.INSTANCE as its NavigationHandler if it needs custom movement code beyond what the built-in actions provide.

AzureCortex now ships a crawl-capable example alongside the zombie and skeleton: CortexSpiderEntity, in com.azure.azurecortex.example.spider. See Wall-Climbing Example (Spider) for the full walkthrough (goal enum, planner, tree, entity wiring, and the CrawlCapability/MovementCapability implementation) built entirely from the pieces documented on this page, including CrawlToDestinationAction, a new action that example adds for actually driving movement across walls and ceilings (see Built-in Actions).

Next

Clone this wiki locally