Sentinel Navigation
Overview
The SentinelNavClient is a third-party navigation library that provides shared pathfinding and movement for consumer plugins through a single global client instance (_G.SentinelNavClient.client).
It handles pathfinding integration with SentinelNavServer, runtime movement behavior (HSM + Behavior Tree), obstacle probing and avoidance-zone memory, and in-game settings UI and visualization.
The original developer of the navigation system is not part of the internal PS team. This is a third-party plugin and the API documented here could be outdated. This documentation is based on the API as of February 2026 (v0.0.5).
Requires SentinelNavServer running and reachable. Use client:is_server_available() to check before issuing navigation commands.
Quick Start
if not (_G.SentinelNavClient and _G.SentinelNavClient.client) then
core.log_error("SentinelNavClient not loaded")
return
end
local client = _G.SentinelNavClient.client
client:move_to({ x = -8900, y = 560, z = 94 }, function(ok, reason)
if ok then
core.log("Arrived")
else
core.log_error("Navigation failed: " .. tostring(reason))
end
end)
- Do not call
client:update()from consumer plugins — SentinelNavClient drives updates internally. - Do not own navigation tuning in consumer plugins — SentinelNavClient UI owns config.
Global Table
_G.SentinelNavClient
| Field | Type | Description |
|---|---|---|
client | sentinel_nav_client|nil | Live getter for the shared singleton Client |
create(config?) | function | Returns the shared Client. config is accepted but ignored |
ui | table | UI orchestrator module |
JSON | table | JSON utility module |
Helpers | table | General helper utility module |
VERSION | string | Current plugin version |
Movement Commands
client:move_to(target, callback?, opts?)
Syntax
client:move_to(target, callback?, opts?)
Parameters
target(table) —{ x, y, z }destination position.callback(function?) —function(success: boolean, reason: string?)called on completion.opts(table?) — Optional overrides copied to blackboard asopts.*.
Pathfinds to the target and starts navigation. Defers the move while casting (enters navigating.deferred). Otherwise transitions into navigating.awaiting_path and the Behavior Tree requests a path.
Example Usage
client:move_to({ x = -8900, y = 560, z = 94 }, function(ok, reason)
if ok then
core.log("Arrived at destination")
else
core.log_error("Move failed: " .. tostring(reason))
end
end)
client:move_direct(target, callback?)
Syntax
client:move_direct(target, callback?)
Parameters
target(table) —{ x, y, z }destination position.callback(function?) —function(success: boolean, reason: string?)called on completion.
Follows a direct single-waypoint path to the target without pathfinding. Useful when the path is known to be clear.
client:follow_path(waypoints, callback?)
Syntax
client:follow_path(waypoints, callback?)
Parameters
waypoints(table[]) — Array of{ x, y, z }positions to follow in order.callback(function?) —function(success: boolean, reason: string?)called on completion.
Follows a precomputed list of waypoints. Commonly used to execute a route returned by plan_route.
client:plan_route(nodes, callback?, opts?)
Syntax
client:plan_route(nodes, callback?, opts?)
Parameters
nodes(table[]) — Array of{ x, y, z }positions to visit.callback(function?) —function(success: boolean, data: table?)called with route data.opts(table?) — Optional route options (e.g.{ return_to_start = false }).
On success, data contains:
| Field | Type | Description |
|---|---|---|
waypoints | vec3[] | Ordered waypoints for the full route |
visit_order | integer[] | 1-based visitation order |
leg_boundaries | integer[] | Waypoint indices where legs start |
leg_distances | number[] | Distance of each leg |
total_distance | number | Total route distance |
On failure, data may be nil or { error = string }.
Plans a TSP (Traveling Salesman Problem) route through the given nodes. This is a planning-only call — it does not execute the route. To execute the route, call client:follow_path(data.waypoints, ...) with the returned waypoints.
Example Usage
local nodes = {
{ x = -9100, y = 400, z = 93 },
{ x = -9200, y = 500, z = 91 },
{ x = -8900, y = 600, z = 95 },
}
client:plan_route(nodes, function(ok, data)
if not ok then
core.log_error("Route plan failed: " .. tostring(data and data.error))
return
end
client:follow_path(data.waypoints, function(success, reason)
if success then
core.log("Route execution complete")
else
core.log_error("Route execution failed: " .. tostring(reason))
end
end)
end, {
return_to_start = false,
})
client:validate_destination(target, callback)
Syntax
client:validate_destination(target, callback)
Parameters
target(table) —{ x, y, z }destination position.callback(function) —function(reachable: boolean, reason: string?, distance: number?).
Reachability probe without starting movement. Use this to check if a destination is reachable before committing to navigation.
Example Usage
client:validate_destination(target, function(reachable, reason, distance)
if not reachable then
core.log_warning("Target unreachable: " .. tostring(reason))
return
end
client:move_to(target, function(ok, fail_reason)
if ok then
core.log("Arrived (" .. string.format("%.1f", distance or 0) .. " yd)")
else
core.log_error("Move failed: " .. tostring(fail_reason))
end
end)
end)
client:replan(reason?)
Syntax
client:replan(reason?)
Parameters
reason(string?) — Optional reason for the replan (for logging).
Re-requests the current path to the current destination by clearing path state and returning to awaiting_path.
client:stop()
Syntax
client:stop()
Stops all movement and resets the active navigation state.
State and Progress
client:get_state()
Syntax
client:get_state() -> string
string— One of:"idle","navigating","arrived","failed".
client:get_full_state()
Syntax
client:get_full_state() -> string
string— Dot-joined hierarchical state (e.g."navigating.recovering.strafing").
client:is_moving()
Syntax
client:is_moving() -> boolean
boolean—trueonly while the top-level state isnavigating.
client:get_destination()
Syntax
client:get_destination() -> vec3|nil
vec3|nil— The current navigation destination, ornilif idle.
client:get_current_path()
Syntax
client:get_current_path() -> vec3[]|nil
vec3[]|nil— The current list of waypoints being followed, ornil.
client:get_path_index()
Syntax
client:get_path_index() -> number
number— The index of the waypoint currently being approached.
client:get_progress()
Syntax
client:get_progress() -> sentinel_nav_progress
| Field | Type | Description |
|---|---|---|
percent | number | Progress from 0 to 1 |
waypoints_remaining | number | Waypoints left |
total_waypoints | number | Total waypoints in path |
current_index | number | Current waypoint index |
Example Usage
if client:is_moving() then
local full = client:get_full_state()
local p = client:get_progress()
core.log(string.format(
"[%s] %.0f%% (%d/%d)",
full,
(p.percent or 0) * 100,
p.current_index or 1,
p.total_waypoints or 0
))
end
client:get_corridor_widths()
Syntax
client:get_corridor_widths() -> number[]|nil
number[]|nil— Corridor widths per waypoint, ornilif not available.
Server Queries
client:is_server_available()
Syntax
client:is_server_available() -> boolean
boolean—truewhen the server connection flag is up.
Reflects the blackboard connection flag maintained by NavigationService. Set true after successful responses, marked disconnected after repeated failures.
client:health_check(callback)
Syntax
client:health_check(callback)
Parameters
callback(function) —function(ok: boolean)called with server health result.
Example Usage
if not client:is_server_available() then
client:health_check(function(ok)
if ok then
core.log("SentinelNavServer reachable")
else
core.log_warning("SentinelNavServer unavailable")
end
end)
end
client:get_height(pos, callback)
Syntax
client:get_height(pos, callback)
Parameters
pos(table) —{ x, y, z }position.callback(function) — Callback with navmesh height at position.
client:get_player_height(callback)
Syntax
client:get_player_height(callback)
Parameters
callback(function) — Callback with navmesh height at current player position.
client:get_all_heights(pos, callback, opts?)
Syntax
client:get_all_heights(pos, callback, opts?)
Parameters
pos(table) —{ x, y, z }position.callback(function) — Callback with multi-layer heights at the XY position.opts(table?) — Optional options.
client:get_player_all_heights(callback, opts?)
Syntax
client:get_player_all_heights(callback, opts?)
Parameters
callback(function) — Callback with multi-layer heights at the player position.opts(table?) — Optional options.
Events
Legacy Events (client:on / client:off)
client:on("arrived", function()
core.log("Arrived at destination")
end)
client:on("failed", function()
core.log_error("Navigation failed")
end)
client:on("state_change", function(data)
core.log("State: " .. tostring(data.from) .. " -> " .. tostring(data.to))
end)
client:on("stuck", function()
core.log_warning("Stuck detected")
end)
| Event | Payload |
|---|---|
"state_change" | { from: string, to: string } |
"arrived" | — |
"stuck" | — (mapped from navigating.recovering) |
"failed" | — |
EventBus Events
For more granular control, use the EventBus:
local bus = client:get_event_bus()
bus:on("nav.state_changed", function(data, event_name)
core.log(event_name .. ": " .. tostring(data.from) .. " -> " .. tostring(data.to))
end, { owner = self, priority = 50 })
bus:on_pattern("nav.server_*", function(data, event_name)
core.log("Server event: " .. event_name)
end, { owner = self })
-- Cleanup:
bus:off_owner(self)
Available Events:
| Event | Description |
|---|---|
nav.state_changed | State machine transition |
nav.arrived | Destination reached |
nav.failed | Navigation failed |
nav.stuck_detected | Stuck condition detected |
nav.stuck_recovered | Recovered from stuck |
nav.deviation_detected | Path deviation detected |
nav.repath_started | Repath in progress |
nav.repath_completed | Repath finished |
nav.obstacle_detected | Obstacle encountered |
nav.path_requested | Path request sent to server |
nav.path_received | Path response received |
nav.path_failed | Path request failed |
nav.waypoint_reached | A waypoint was reached |
nav.server_connected | Server connection established |
nav.server_retry | Server retry attempt |
nav.server_disconnected | Server connection lost |
nav.server_error | Server error occurred |
EventBus Methods:
| Method | Description |
|---|---|
bus:on(event, callback, opts?) | Subscribe to event |
bus:once(event, callback, opts?) | Subscribe once |
bus:on_pattern(pattern, callback, opts?) | Subscribe to pattern (e.g. "nav.server_*") |
bus:off(id) | Unsubscribe by ID |
bus:off_owner(owner) | Unsubscribe all by owner |
bus:pause() | Pause event delivery |
bus:resume() | Resume event delivery |
State Model
Top-Level States
| State | Description |
|---|---|
idle | No active navigation |
navigating | Actively navigating |
arrived | Destination reached |
failed | Navigation failed |
Navigating Substates
| Substate | Description |
|---|---|
awaiting_path | Waiting for path from server |
following_path | Following the active path |
recovering | Attempting stuck recovery |
repathing | Re-requesting the path |
deferred | Move deferred (e.g. while casting) |
Recovery Sub-Substates
| Sub-Substate | Description |
|---|---|
jumping | Attempting to jump over obstacle |
probing | Probing for alternative path |
strafing | Strafing sideways |
backtracking | Moving backwards |
Failure Reasons
| Reason | Description |
|---|---|
unreachable | Destination cannot be reached |
server_timeout | Server did not respond |
max_stuck_exceeded | Too many stuck recovery attempts |
max_repath_exceeded | Too many repath attempts |
Advanced APIs
These are lower-level services exposed on the client. They are more likely to change than the high-level Client API.
Accessible as fields on the client:
client.nav_client— NavigationService (core pathing, spatial queries, tactical)client.movement— MovementService (waypoint following, strafe, speed)client.obstacle— ObstacleService (probing, avoidance zones)
NavigationService (client.nav_client)
| Method | Description |
|---|---|
find_path(start, dest, callback, opts?) | Core A* pathfinding |
find_route_tsp(nodes, callback, opts?) | TSP route planning |
find_route_multi(stops, callback, opts?) | Multi-stop route |
check_path(pos, waypoints, callback, opts?) | Validate existing path |
find_path_corridor(start, dest, callback, opts?) | Path with corridor widths |
find_path_avoid(start, dest, zones, callback, opts?) | Path avoiding zones |
raycast(start, dest, callback, opts?) | Navmesh raycast |
get_height(pos, callback, opts?) | Navmesh height query |
get_all_heights(pos, callback, opts?) | Multi-layer height query |
random_point(callback, opts?) | Random reachable point |
flee(player_pos, threats, callback, opts?) | Flee from threats |
kite(player_pos, target_pos, callback, opts?) | Kite a target |
health_check(callback) | Server health ping |
is_available() | Server connection status |
NavigationService.is_indoor() | Indoor detection (static) |
NavigationService.get_continent_id() | Current continent ID (static) |
MovementService (client.movement)
| Method | Description |
|---|---|
navigate(waypoints) | Start following waypoints |
process() | Tick movement processing |
stop() | Stop movement |
get_current_index() | Current waypoint index |
get_remaining_waypoints() | Remaining waypoint count |
is_moving() | Is movement active |
strafe(direction) | Strafe in a direction |
apply_dynamic_speed(blackboard?) | Apply dynamic speed adjustments |
ObstacleService (client.obstacle)
| Method | Description |
|---|---|
probe_forward(player_pos, target_pos) | Probe ahead for obstacles |
probe_segment(pos_a, pos_b) | Probe a line segment |
probe_path_ahead(waypoints, max_segments?) | Probe multiple segments ahead |
add_zone(pos, radius?) | Add avoidance zone |
remove_zone(index) | Remove avoidance zone |
get_avoidance_zones() | Get all avoidance zones |
get_zone_count() | Number of active zones |
clear() | Clear all zones |