Skip to content

Sensing

AzureDoom edited this page Aug 29, 2026 · 1 revision

Sensing

Sensors periodically refresh perception state onto the blackboard so the planner, tree, and actions don't each have to re-derive it. CortexRuntime ticks exactly one optional Sensor<E> per agent per tick (skipped while the running action is LOCKED/EMERGENCY).

@FunctionalInterface
public interface Sensor<E> {
    void tick(E agent, Blackboard blackboard);
}

TargetSensor, finding and tracking a target

The main sensor most agents need. It periodically evaluates a Selector and writes the result to CommonBlackboardKeys.TARGET.

var validity = VanillaTargetPredicates.players()
    .or(VanillaTargetPredicates.abstractVillagers())
    .or(VanillaTargetPredicates.ironGolems());

var targetSensor = new TargetSensor<MyEntity>(
    TargetSensor.nearestMatching(24.0D, validity), // Selector: retain current if still valid, else nearest match
    10,                                              // retarget every 10 ticks (or immediately if target dies)
    TargetSensor.lineOfSight()                       // visibility predicate, enables last-seen tracking
);
  • retargetInterval, forced re-evaluation cadence. The sensor still re-checks immediately whenever the current target dies or becomes invalid, regardless of this interval.
  • visibilityPredicate (optional, third constructor argument), tested every tick against the current target. While it holds, the sensor additionally maintains LAST_SEEN_POS / LAST_SEEN_VELOCITY / LAST_SEEN_TICK, frozen at the moment visibility was last confirmed, as opposed to LAST_KNOWN_TARGET_POS, which updates unconditionally every tick regardless of visibility. Agents constructed without a predicate never populate these three keys. TargetSensor.lineOfSight() supplies the common Mob#hasLineOfSight check; mods with unusual vision rules (echolocation, blindness effects, seeing through thin walls) should supply their own.

Building a Selector

TargetSensor.nearestMatching(range, validity) covers the common case, retain the current target if validity still accepts it, otherwise scan for the nearest entity within range blocks that does:

public static <E extends Mob> Selector<E> nearestMatching(double range, Predicate<LivingEntity> validity)

VanillaTargetPredicates ships a few reusable building blocks matching vanilla's own targeting rules — players(), abstractVillagers(), ironGolems(), babyTurtlesOnLand(), combine them with Predicate.or.

Write your own Selector directly when "nearest matching" isn't the rule you want (e.g. prefer a specific target type, or weight by something other than distance).

TargetPrediction, searching where a lost target probably went

Pairs with TargetSensor's visibility-gated tracking. Instead of an investigate action always walking to the exact block a target occupied when last seen, predictInterceptPosition extrapolates forward along its last-known velocity:

var searchPoint = TargetPrediction.predictInterceptPosition(
    lastSeenPos, lastSeenVelocity, lastSeenTick, currentTick,
    100,    // maxStalenessTicks, give up extrapolating beyond this age
    0.02D,  // minSpeed, below this, treat the target as stationary
    2.0D,   // minDistance
    8.0D,   // maxDistance
    TargetPrediction.standableIn(level)  // rejects predictions embedded in a wall, etc.
);

Falls back to the raw lastSeenPos whenever extrapolation isn't warranted: no recorded velocity/tick, the sighting is too stale, the target was essentially stationary, or the projected point fails the standable check. TargetPrediction.standableIn(level) is the ordinary ground-mob predicate (open space with solid footing below); mods with different footprints (wall-crawlers, flyers) should supply their own.

See InvestigateLastSeenTargetAction in Built-in Actions for the ready-made action built on this.

EnvironmentSensor, cheap ambient queries

Not a periodic Sensor, these are cheap enough to call directly wherever needed rather than caching every tick:

EnvironmentSensor.isInDarkness(mob, 4);   // ambient light <= threshold
EnvironmentSensor.isInWater(mob);
EnvironmentSensor.healthFraction(mob);    // in [0, 1]

HazardSensor, precomputed danger-nearby flag

A thin Sensor wrapper around MovementController.hasNearbyDangerEntity (see Navigation and Pathfinding), for when a behavior tree wants the flag pre-computed on the blackboard rather than recomputing the repulsion scan itself:

public static final BlackboardKey<Boolean> NEAR_HAZARD = BlackboardKey.of("near_hazard", Boolean.class);
var hazardSensor = new HazardSensor<MyEntity>(NEAR_HAZARD);

Most agents don't need this dedicated sensor, call MovementController.hasNearbyDangerEntity directly from an action or planner if you only need the value in one place.

Next

Clone this wiki locally