Kzeroko KTM2

NPC locomotion

Ground gait, navigation caches, obstacle flight, landing and swimming contracts for NPC controllers.

Updated Sep 30, 2026 21 min read

NPC movement combines a continuous ground-speed solver with a phase-based flight/swim controller. These are internal runtime classes under internal.ai.travel, not public API facades; use them when extending the mod’s NPC implementation.

What it does

NpcGaitController chooses navigation speed and sprint presentation separately. NpcNavigation owns paths and cached air/swim waypoints. NpcMoveControl applies ground, flight, landing and water steering, while NpcStepAssist supplies the same step height used by the entity’s movement and pathing.

The base entity installs NpcConfig.flightSettings and default swim settings. canNpcFly() returns false; canNpcSwim() and canBreatheInWater() return true. Citizens, simple NPCs and adventurers therefore cannot fly or hover by default. A flying subclass must explicitly override canNpcFly() to return true; Evelina does so. Flight settings only tune that capability. Formation slots, ladder/parkour ownership and move commands are separate follower concerns.

setNpcFlying and setNpcHovering reject activation without flight capability, including when restoring tracked flight flags from NBT. setManualFlight also rejects unsupported requests. The shared animation kit contains no fly or hover clip; a flying NPC supplies both in its entity animation file. See NPC assets.

Runtime model

Ground gait

Follow travel, ground commands and combat approach share the entity’s gait controller. Follow supplies the player’s sprint flag and no urgency. Ground commands and combat request urgency only when sprintRange > 0 and goal distance reaches that range. A held follow position relaxes the solver; exclusive traversal resets it. Ending the follow task clears its movement/look memories, traversal state and sprint flag.

The current demand calculation includes a gap-scaled ceiling. steps is elapsed ticks, except resumed sampling uses 1. A stale sample refreshes distance/position and closing rate; it does not itself call the full reset().

gap = max(0, filteredDistance - max(0, holdDistance))
desiredClosing = min(sqrt(2 × 0.005 × gap), 0.24)
ceiling = clamp(0.25 + gap × 0.5, 0, 1)
target = clamp(demand + (desiredClosing - closingRate) × 0.35, 0, ceiling)
if urgent: target = max(target, min(0.75, ceiling))
demand = clamp(demand + clamp(target - demand, -0.08 × steps, 0.08 × steps), 0, 1)
walk = max(0.15, walkSpeed)
sprint = max(walk, sprintSpeed)
blended = lerp(demand, walk, sprint)
travelSpeed = sprinting ? blended / 1.3 : blended

The sprint flag is published through setNpcSprinting; this implementation compensates its navigation speed with the fixed divisor 1.3. apply() writes the existing path’s speed through setTravelSpeed(), without replacing the path. relax() reduces demand while preserving history; reset() clears history, gait and cached outputs.

ConstantValueEffect
ARRIVAL_DECELERATION0.005Arrival deceleration coefficient.
MAX_CLOSING_RATE0.24Maximum desired closing rate, blocks/tick.
TRACK_GAIN0.35Closing-rate error gain.
MAX_DEMAND_RATE0.08Maximum demand change per elapsed tick.
DEMAND_CEILING_BASE / DEMAND_CEILING_PER_BLOCK0.25 / 0.5Gap-scaled ceiling coefficients.
DISTANCE_LERP / CLOSING_LERP / GROUND_SPEED_LERP0.45 / 0.35 / 0.30Distance, closing rate and horizontal-speed filters.
STALE_TICKS6Nonpositive elapsed time or a gap greater than this resumes tracking without measuring a jump in position.
HOLD_DEMAND / HOLD_GROUND_SPEED0.05 / 0.015Both demand and filtered ground speed must be below these to request HOLD.
GAIT_MIN_DWELL_TICKS12Minimum ticks between gait changes.
SPRINT_ENTER_DEMAND / SPRINT_EXIT_DEMAND0.60 / 0.42Normal sprint demand entry/continuation thresholds.
LEADER_SPRINT_ENTER_DEMAND / LEADER_SPRINT_EXIT_DEMAND0.32 / 0.18Thresholds while the followed player sprints.
SPRINT_GAP_FACTOR / SPRINT_GAP_MIN / SPRINT_GAP_MAX0.08 / 0.7 / 1.6Entry gap is clamp(sprintRange × factor, min, max), in blocks.
SPRINT_GAP_EXIT_FACTOR0.5Continuation gap is this fraction of the entry gap.
SPRINT_ENTER_SUSTAIN_TICKS16Demand and distance gates must remain true for this many ticks before entering SPRINT.
URGENT_DEMAND_FLOOR0.75Urgency raises the target only up to the gap-scaled ceiling.
MIN_TRAVEL_SPEED / SPRINT_ATTRIBUTE_BOOST0.15 / 1.3Effective walk floor and divisor used while sprinting.

The effective gait is HOLD, WALK or SPRINT. Demand and distance must both pass before sprint entry; dwell also applies when leaving a gait. HOLD is a presentation result, not a command to stop navigation: the owning task must clear its walk target or stop the path when appropriate.

The navigation uses an amphibious node maker, allows door paths/open doors and swimming, accepts water path types, and rejects lava and open path types. A complete vanilla path wins. Otherwise a custom fallback is tried; an incomplete custom result replaces vanilla only when vanilla returned no path.

Fallback mode follows actual state: air while flying; swim when the NPC can swim and either its position or target is wet; ground otherwise. Ground reachability deliberately calls the vanilla ground search and custom ground search directly, so a possible flight route cannot make a blocked walk route appear reachable.

SearchPoll budgetBounds expansion around start/targetNeighbours
Ground2800Horizontal 36, vertical 8 blocksHorizontal cardinal/diagonal directions, trying vertical offsets 0, 1, -1, -2.
Air4200Horizontal 48, vertical 24 blocks26 adjacent directions.
Swim3200Horizontal 36, vertical 12 blocks26 directions generated by horizontal neighbours with vertical variants and vertical-only moves.

These ranges expand the bounding box between endpoints; they are not maximum travel distances. Exhausted searches may return partial paths. Ground candidates require support, no feet water/lava, loaded body chunks and body clearance. Air candidates require loaded clearance; swim candidates accept water at feet/head and may also use standable nodes. Segment clearance samples the translated body box at intervals no greater than 0.5 blocks.

Ground reachability reuses its result for less than 20 ticks while the destination stays within 1.5 blocks. A moved destination bypasses that cache. Air/swim routes rebuild when absent, finished, mode-changed, target moved more than 1.5 blocks, age exceeds 30 ticks, or the next segment loses clearance. Thus 30 ticks is an age limit, not a minimum interval between searches. Missing routes are not negatively cached.

Clear direct travel returns the destination without a route. Waypoint completion radii are 0.85 blocks for air and 0.7 for swim; selection looks up to 7 nodes ahead for a clear segment. A client waypoint query simply returns its requested destination. Starting/stopping normal navigation clears route caches. stopGroundNavigation() releases only the walking path, preserving air/swim routes during movement ownership transfer.

POI movement locks block new navigation. During a move command, unrelated entity targets and positions outside 1.5 blocks of its target are rejected. The dedicated follower-traversal entry point accepts an intermediate path while retaining the POI lock check.

Flight phases and takeoff

The phases are GROUND, AIR_TAKING_OFF, AIR_FLYING, AIR_GLIDING, AIR_HOVERING and WATER_SWIMMING. Air phases disable gravity and publish flying/hovering flags. Ground and swimming restore gravity; controller ticks clear fall distance. Death clears manual flight, routes, landing and obstacle state.

Flight requires flight capability and no combat target. Exclusive ground traversal suppresses ordinary flight requests; distant-owner rescue can release that traversal. Swimmable water normally blocks flight. The request can come from an airborne move command, manual toggle, distant owner, obstacle recovery, configured fluid escape or the special water takeoff. A command/anchor hold suppresses manual and ordinary owner-follow flight.

Airborne escort recognises an owner flying or gliding. With preferFlight = true or a distant-owner rescue request, it also recognises unsupported movement more than 1.5 blocks above a surface. flyToOwner must be enabled for either case. Ground contact, climbing and water contact use the ground/swim rules instead. NpcFollowerAirSpace supplies the same destination policy to formation and movement: follow clear air slots at owner height, hover on arrival and face the actual owner while hovering. Flight owns movement through descent: it clears ground movement and jump inputs, and suppresses walking path execution. BaseNpcEntity.turnHead retains controller yaw for the airborne body instead of deriving a backwards walking correction from displacement. Landing demand resumes when the owner returns to ground; an owner taking flight can interrupt a committed descent.

Within the configured follow range, preferFlight = false prevents ordinary jumps and falls from triggering automatic takeoff. The follow task retains ground control while walking or confirming a blocked route, and tries ladders and parkour before releasing automatic flight. A started traversal retains exclusive control within follow range. Ordinary distance-based takeoff also rejects a reachable walking destination. Manual flight, air commands and water escape keep their separate eligibility rules.

Distant-owner rescue uses actual three-dimensional NPC-to-player distance against NpcConfig.followRange (32.0 blocks by default). Beyond that range, either flight preference permits takeoff without the ordinary warmup, walking-reachability veto or traversal deferral. It still requires flyToOwner, flight capability, an alive same-world owner, FOLLOW mode, no command hold or combat target, and an awake, unmounted NPC whose follow is not paused for dialogue. Water eligibility still applies. A committed descent can be interrupted by this rescue. Teleport recovery gives the flight attempt 40 ticks (2 seconds) before applying its existing idle gates.

Ordinary owner-follow flight also requires FOLLOW, a present owner, neither participant climbing and distance beyond its start range. Once outside GROUND, it uses the stop range. The implementation declares a fluid warmup of 60 ticks, but the earlier touching-water swimmer guard prevents that water case from reaching it; it can still apply to a swimming-capable NPC in lava.

Obstacle flight first resolves a ground-command destination or formation slot. Air/swim commands and arrived command anchors do not request this recovery. It requires a ground stall, failed ground reachability and a horizontal or absolute vertical gap above the threshold. The candidate landing pad must improve horizontal distance plus absolute vertical distance by the required gain. The latch survives proximity; touchdown, invalid eligibility, sustained stall or the hard cap can clear it.

Water takeoff requires a swimming-capable flyer in water, a dry non-swimming followed player above it, FOLLOW, no target and no command hold. It needs the far-follow warmup or at least 20 idle ticks. It supplies upward and forward velocity and renews a temporary allowance to publish flying while still in water. An owner swimming in water otherwise suppresses flight unless the command or water-takeoff exceptions apply.

ConstantValueEffect
FOLLOW_FLY_WARMUP_TICKS / FOLLOW_FLY_WARMUP_NO_SIGHT_TICKS30 / 45Distant-owner warmup; loss of sight raises the requirement.
STUCK_ESCAPE_TICKS40At this idle count, the distant-owner warmup becomes 10 ticks.
GROUND_STALL_TICKS / GROUND_BLOCKED_TAKEOFF_TICKS12 / 8Ground stall threshold, then consecutive failed-ground-reach ticks for obstacle flight.
GROUND_BLOCKED_MIN_GAP / FLIGHT_MIN_GAIN1.75 / 1.5Obstacle gap threshold and required improvement in horizontal distance plus absolute vertical distance.
OBSTACLE_FLIGHT_MAX_TICKS / OBSTACLE_FLIGHT_COOLDOWN_TICKS300 / 60Obstacle latch hard cap and cooldown after completed landing.
FLIGHT_ROUTE_REFRESH_TICKS / FLIGHT_ROUTE_RETRY_TICKS30 / 30Destination-cache age and failed-resolution retry delay; anchor movement can invalidate a current destination earlier.
FLIGHT_ROUTE_ANCHOR_TOLERANCE_SQ / FLIGHT_PAD_RADIUS3.0 × 3.0 / 4Anchor tolerance squared and route landing-pad ring radius in blocks.
FLIGHT_APPROACH_HEIGHT0.6Waypoint offset above the landing pad, blocks.
POSITION_GAIN / RESPONSE0.16 / 0.5NpcArrivalMotion desired velocity gain and velocity interpolation. No minimum arrival speed.
LANDING_HANDOFF_RANGE_SQ / LANDING_HANDOFF_MIN_HEIGHT / LANDING_HANDOFF_MAX_HEIGHT0.55 × 0.55 / 0.0 / 3.0Landing handoff requires horizontal range and this signed height band above the pad.
FACING_MIN_HORIZONTAL_SQ / FLIGHT_TURN_RATE0.5 × 0.5 / 12.0Ignore shorter horizontal direction vectors; otherwise turn at most this many degrees/tick.
FLIGHT_PROGRESS_EPS_SQ / FLIGHT_STUCK_TICKS0.05 × 0.05 / 20Insufficient position change for this many ticks invalidates the air route and invokes stall handling.
FLIGHT_STALL_HOVER_TICKS / HOVER_LANDING_TIMEOUT_TICKS10 / 40Consecutive stall calls trigger hover; descent evaluation widens pad search after sustained hover.
LANDING_PAD_REFRESH_TICKS / LANDING_PAD_RADIUS / LANDING_PAD_WIDE_RADIUS20 / 3 / 6Pad cache age and normal/wide search radii; anchor changes or widening can bypass the age cache.
LANDING_DESCENT_SPEED / LANDING_HORIZONTAL_SPEED-0.20 / 0.30Padless descent and maximum horizontal approach, blocks/tick.
LANDING_APPROACH_LERP / LANDING_ALIGNED_RANGE0.35 / 0.85Horizontal interpolation and alignment radius in blocks.
LANDING_ALIGN_TIMEOUT_TICKS60After this many landing ticks, descent proceeds despite incomplete alignment.
FLIGHT_ARRIVAL_RANGE / RELEASE_PADDING0.45 / 0.55Direct flight enters hover at the arrival radius; resumption requires arrival radius plus padding.
LANDING_TURN_RATE / LANDING_FACING_HOLD_TICKS / LANDING_SETTLE_SPEED_SQ15.0 / 12 / 0.06 × 0.06Owner-facing turn rate, post-landing hold ticks and horizontal speed that cancels the hold.
WATER_TAKEOFF_MIN_VERTICAL_GAP / WATER_TAKEOFF_WARMUP_TICKS / WATER_TAKEOFF_FLIGHT_ALLOW_TICKS3.0 / 12 / 24Owner-above gap, far-follow warmup and renewable water-flight allowance.
WATER_TAKEOFF_UPWARD_VELOCITY / WATER_TAKEOFF_FORWARD_VELOCITY0.36 / 0.12Initial minimum upward speed and added forward speed, blocks/tick.

Destination flight and landing

Ground-destination flight stores a landing pad and the anchor used to resolve it. Airborne escort, manual flight, airborne commands, active water takeoff and fluid escape use direct waypoint steering instead. A current destination is reused only while younger than 30 ticks and its anchor stays within 3.0 blocks; either condition failing allows another pad search. When refresh fails, an existing route is retained. Failed initial resolution waits 30 ticks.

For stationary destinations and routed waypoints, NpcArrivalMotion tapers desired velocity toward zero with remaining distance and blends velocity each tick. The step is capped by both maximum speed and remaining distance. Hover arrival tests the final destination, independently of route waypoint completion; an intentional follow hold never falls through to an old walking target. Travel facing uses horizontal velocity when the waypoint has a meaningful horizontal offset, with the existing bounded turn rate.

NpcFlightFollowMotion samples owner position once per world tick, including vertical movement. Speed at least 0.02 blocks/tick keeps moving pursuit active; holding is allowed after 8 stationary ticks (0.4 seconds). Owner velocity is filtered with 0.35; samples separated by more than 5 ticks or 8.0 blocks reset the estimate. Moving pursuit bypasses cohesion and arrival holds.

Direct, unobstructed escort adds sampled owner velocity to position correction and caps steering acceleration at 0.06 blocks/tick². Escort destinations blend by 0.35, resetting across changes over 8.0 blocks. The filtered destination is clearance-checked; route waypoints are not interpolated, and obstacle detours use ordinary arrival steering without owner-velocity addition.

NpcLandingResolver searches rings nearest first. Within the first usable ring, score is horizontal squared distance plus absolute vertical difference × 1.75. It scans at most 10 supporting blocks per column, starting at floor(anchorY), and resolves their collision surface heights, including partial-height blocks. Loaded support and feet chunks, dry support/feet and body clearance are required. Descent-corridor checks sample every 0.5 blocks up to 24 blocks above the pad, clamping negative heights to 0; this is a bounded clearance check, not proof that a complete flight route exists.

The route follows live air waypoints toward a point 0.6 blocks above the pad. Handoff requires the horizontal and signed vertical band in the table. A committed pad remains fixed while support and body clearance remain valid. Gliding pauses vertical motion while aligning, then descends; after 60 ticks it descends even without alignment. Descent speed is -min(0.20, max(0.035, heightAbovePad × 0.2)) blocks/tick. Alignment never requests an upward impulse.

A resolved-pad descent is committed. Airborne escort, distant-owner rescue, deep/free swimming or an aquatic command can interrupt it; ordinary renewed flight demand cannot. Physical ground contact completes landing. Touchdown stops navigation, restores gravity, clears horizontal velocity and removes positive vertical velocity. Followers turn toward their owner at the bounded settling rate; commanded NPCs retain travel facing. The owner-facing hold stops early when horizontal motion exceeds its threshold.

Missing waypoints damp velocity and count stalls. The progress watchdog also invalidates the route when displacement stays below its threshold. A successful steering tick resets the consecutive stall counter before that watchdog runs, so repeated watchdog triggers alone do not guarantee the hover threshold will accumulate. In descent evaluation, sustained hover widens the landing search and ultimately starts vertical descent even without a pad. New flight requests can still change the phase; this is not an unconditional bound on total airborne time.

Swimming and presentation

Swimming requires enabled settings and capability, with submerged/deep water or an aquatic command in contact with water. A current airborne-free swim phase may continue with fluid height at least 0.35 while not grounded. Shallow foot contact alone does not force the free-swim phase.

Command targets take precedence. An arrived aquatic hold has no movement target. Ordinary follow swimming stops within SwimSettings.followStopRange; when the owner is dry, the controller probes nearby dry land every 20 ticks and tightens the remaining follow stop distance to at most 1.0 block. A failed swim waypoint search falls back to the raw requested target.

Active swimming is a separate tracked animation state: entry uses NpcConfig.swimRange, continuation uses swimStopRange, and a changed decision must persist for 3 ticks. Nonpositive thresholds use 0.2 blocks. Clearing a target clears this state; the state itself does not stop the steering calculation.

The base entity’s swim visual has its own hysteresis:

Visual ruleValue / behaviour
Water-height entry / continuation0.5 / 0.12 blocks, alongside submerged/swimming/command checks.
Entry stability2 ticks; aquatic commands use 1.
Minimum visual hold30 ticks; continued contact renews it to at least 15.
Exit stability12 ticks after the minimum hold expires.
Immediate exitCapability lost, flying, riding, sleeping, dead, or grounded with no submersion and water height below 0.12 blocks.

Water contact also samples the entity column at offsets 0.05, 0.45, 0.95, and -0.15 while already in swimming pose. Visual state controls SWIMMING versus STANDING pose. Water movement input is divided by the movement attribute with a floor of 1.0E-5.

Shore assistance applies within horizontal distance 1.65 and vertical delta -0.75–1.5 blocks when the target fluid is empty. It activates jumping and adds 0.035 horizontal and 0.08 vertical velocity; upward input is at least 0.7 of the scaled swim speed.

Aquatic wandering can replace a block target’s height with the NPC’s current height if that water position has clearance, only in WANDER/WANDER_BY_RADIUS without command/anchor or flight. Arrival uses at least 2.0 blocks horizontal range and an aquatic target column. The qualifying condition already requires water contact, so the current successful branch does not enforce the helper’s later 3.0 vertical fallback.

Contracts

NPC configuration

These are Java record/builder fields in NpcConfig, not data-pack JSON. Other combat, squad and emotion fields are covered by their owning systems. Unlisted numeric clamps are not performed by the record constructor; do not assume arbitrary or non-finite custom input is validated.

FieldTypeDefaultWhat it does
walkSpeeddouble1.0Ground navigation scale; solver uses at least 0.15. Constructor does not clamp this field.
sprintSpeeddouble1.35Upper blended navigation scale; solver raises it to at least effective walkSpeed.
sprintRangedouble12.0Blocks; command/combat urgency requires a positive value and distance at least this value. Also scales the gait gap gate.
swimRangedouble3.0Active-swim pose entry distance in blocks; constructor clamps to at least 0.
swimStopRangedouble1.6Active-swim pose exit distance; clamped to 0–swimRange.
followRangedouble32.0Follow task range in blocks; formation/leash policy may narrow it.
wanderRangedouble8.0Wander radius configuration in blocks.
stepHeightdouble1.25Blocks; constructor clamps to 0.5–2.0. Resolved step height still respects the supplied vanilla height.

While riding or flying, step assist returns the supplied vanilla height. A swimming-capable NPC touching water uses at least 1.0; other ground movement uses at least configured stepHeight. Jump activation compares required climb to the resolved step height plus the shared collision epsilon.

Flight settings

NpcConfig.flightSettings supplies each NPC’s immutable flight settings. The builder uses NpcMoveControl.FlightSettings.defaultSettings() by default; null is normalised to those defaults. NpcMoveControl.FlightSettings.defaultSettings() supplies the values below. The canonical constructor and createSettings store values directly without range or finite-number checks; ranges are squared by the controller.

FieldTypeDefaultWhat it does
flyToOwnerbooleantrueEnables distant-owner flight and water takeoff eligibility.
preferFlightbooleanfalsePrefer eager automatic flight over ground-route and traversal acquisition; enables unsupported-owner air following. Beyond followRange, distance rescue applies with either preference.
flyOutOfFluidbooleanfalseRequests fluid escape when in water/lava and unable to use swimming.
followStartRangedouble12.0Ground takeoff distance in blocks, compared as a square.
followStopRangedouble4.0Owner-flight continuation distance outside GROUND, squared for comparison.
maxGlideDropRangedouble6.0Downward fallback landing/ground-probe distance in blocks.
hoverRadiusdouble3.0Near-owner distance used when choosing hover after no landing pad is found.
flightSpeedfloat2.0Multiplies movement attribute before arrival taper.

A flight-capable character can select this preference in its Java NPC configuration. Setting preferFlight alone does not grant flight:

NpcConfig config = NpcConfig.builder()
        .flightSettings(NpcMoveControl.FlightSettings.defaultSettings().withPreferFlight(true))
        .build();

The switch is part of the entity’s Java configuration, not saved NBT or a global client setting.

Swim settings

NpcMoveControl.SwimSettings.defaultSettings() supplies these values. Its constructor and factory also store values without validation. disabled() uses false, false, 0, 0, 1.0, 1.0, 0.0 in field order.

FieldTypeDefaultWhat it does
enabledbooleantrueMust also pass canNpcSwim().
buoyantbooleantrueAdds 0.005 blocks/tick to vertical velocity while touching water.
pitchChangeint45Pitch target is clamped to ±45 degrees by default; actual interpolation turns at 5 degrees/tick.
yawChangeint45Maximum yaw change in degrees/tick.
speedInWaterfloat0.1 / 0.3Movement-speed multiplier in water; default is the ratio of the declared player/NPC base scales.
speedInAirfloat1.0Fallback movement-speed multiplier outside touching-water state.
followStopRangedouble4.0Distance to stop choosing the follow swim target; separate from active-swim pose hysteresis.

Ownership and persistence

The controllers, demand history, route caches, takeoff counters and landing pads are runtime-only. Flying, hovering, active swimming and swim visuals use tracked entity flags; normal sprint state is set through the entity. Do not serialise a controller’s transient phase as a replacement for the entity lifecycle.

Use server AI execution for movement mutations. requestGroundTraversal() is a per-control-tick claim that is consumed on the next update; it restores ground/gravity flags if necessary. Reassert it while a traversal owns movement. faceGroundDirection() applies a bounded horizontal turn and holds it through that control tick. setManualFlight(false) removes the manual request; it does not teleport or immediately force a grounded position.

Extending it

Construct the appropriate settings when installing a custom NpcMoveControl, and override capability methods for species restrictions. For a new ground movement owner, update and apply the shared gait once per owned tick, relax on an ordinary hold, and reset on an ownership change. Keep path retargeting separate from per-tick speed publication.

Use canReachOnGround for walking feasibility and the air/swim waypoint methods for those media. Handle null/partial results and target changes; the bounded searches cannot promise a complete route. Resolve step height through the entity instead of maintaining a second jump threshold.

Verifying

Exercise slow walking, sustained sprinting, short sprint bursts, arrival and resumed tracking; inspect both navigation speed and sprint flag. Test ground-command and combat urgency around the configured range, then ownership changes into ladder/parkour traversal.

Check step transitions while walking, riding, flying and leaving water. For flight, test a nearby unreachable ledge, a distant owner, a ceiling, a moved destination, no valid pad, a pad slightly above/below the NPC, and a commanded landing away from the owner. Inspect cache invalidation as well as successful arrival.

For water, test shallow contact, depth transitions, an aquatic arrived hold, a dry owner above water and land exit. Observe phase, active-swim flag and visual pose independently. Reloading an NPC should rebuild runtime movement state rather than resume a saved route.