Walker Engine & Navigation
This page documents the committed source. Treat revision-sensitive RuneLite UI, packet, or in-game outcomes as pending live-client verification unless the page records direct evidence.
This document covers the Walker Engine, Navigation Actions, Walk Assistant plugin, and Movement Transport Actions/Data components. Imported transport dispatch and handler state machines are documented separately in walker-transports.md.
Walker Route Planning & Execution Flow
1. Walker Engine
com.n3plugins.sdk.walker plans and runs paths across the world. A single static walker serves the entire suite, ticked by PacketUtilsPlugin.onGameTick via Walker.tick(). Feature plugins should use the result-aware NavigationActions facade instead of interacting with the Walker directly, unless they require manual path manipulation (like Walk Assistant).
Concurrency and Thread Safety
The static Walker engine uses a ReentrantLock (WALK_LOCK) to serialize concurrent walkTo or walkPath calls from multiple script threads. This ensures state fields (active path, door policy) are not corrupted by racing threads while allowing nested calls (e.g., from action steps). stop() acquires the same lock, so it is safe to invoke from any thread, including client-thread and AWT event contexts.
Static Facade API
WalkerPath path = Walker.walkTo(new WorldPoint(3164, 3486, 0));
if (Walker.isWalking()) {
// Player is currently moving along the path
}
Walker.stop(); // Cancels active path
| Method | Description |
|---|---|
planTo(WorldPoint target) | Builds a WalkerPath without starting movement. |
planTo(WorldPoint target, WalkerRouteOptions options) | Builds a WalkerPath with custom routing options. |
walkTo(WorldPoint target) | Builds and starts a path to a point. |
walkTo(WorldPoint target, WalkerRouteOptions options) | Builds and starts a path using custom routing options. |
walkPath(List<WorldPoint> path) | Executes a specific pre-defined path. |
tick() | Advances the active path; called by PacketUtilsPlugin, not by feature plugins. |
stop() | Cancels and terminates the active path. |
getActivePath() | Returns the running WalkerPath (or null). |
isWalking() | Returns true if a path is active and running. |
User-Initiated Path Clearing
Clearing the path from the Shortest Path plugin UI — the Clear Path menu entry on the world-map marker or the configured clear-path hotkey (CTRL+X by default) — calls Walker.stop() before resetting the preview. Clearing therefore exits and cleans up the currently active WalkerPath instead of leaving it walking after the displayed route is removed. Automated clears (arrival inside reachedDistance, replan cancellation) do not invoke Walker.stop(); those paths terminate through their own lifecycle.
Telemetry
WalkerTelemetry provides a thread-safe ring buffer of recent decision events with a capacity of 256 and suite-level counters. WalkerPath emits one terminal event before Walker clears or replaces the active handle. The event remains available after Walker.getActivePath() returns null.
Ring Buffer Events:
WalkerTelemetry.record()captures the plan request ID, session and lifecycle correlation IDs, game tick, decision, step index, player position, normalized target, walker status, structured failure reason, and elapsed milliseconds.- Terminal completion uses
target-reachedfor exact arrival and retainsaccepted-distance-reachedfor radius arrival. Cancellation useswalker-cancelled; failures retain their planning or bank diagnostic when available. WalkerTelemetry.getRecent(n)returns the newest requested events in chronological order for Agent Server and overlay diagnostics.Walker.getTelemetryEvents(int n)exposes the same bounded history.- Agent Server returns at most 20 events in
recentTelemetry; an idle snapshot does not erase the latest terminal event.
Counters (AtomicLong):
recoveryCount: Movement stalls or action recoveries triggering a replanstallRecalcCount: Stall threshold recalculationsunreachableCount: Endpoints declared unreachable
List<WalkerTelemetry.TelemetryEvent> events = Walker.getTelemetryEvents(50);
// events[0].status, events[0].failureReason, events[0].decision, etc.
Route Options (WalkerRouteOptions)
WalkerRouteOptions.smartDefaults(): Enables walking, teleports, transports, and wilderness avoidance.WalkerRouteOptions.walkingOnly(): Disables all teleports and transport categories, forcing standard walking.- Toll Gate & Fee Actions: Dialogue-based gates (such as the Al-Kharid toll gate) are routed through
TollGateWalkerActionwhich checks fees (e.g. 10 coins), interacts with the gate object, and completes necessary dialogue prompts ("Pay 10 coins", "Can I come through?") before observing gate opening. - Transport Resolver Resilience:
PluginTransportActionResolveruses fuzzy display name matching for minigame teleports and normalized spell key lookups, logging diagnostic warnings if unresolvable transport metadata is encountered. - Vessel destination recovery: Boat and ship actions do not complete while a destination-side gangplank crossing remains. If the following land step has no usable route or cannot queue movement, Walker makes one nearby disembarkation attempt, waits up to three ticks for movement, then resumes pathing or returns to normal replanning.
- Master Switches:
useTeleportsanduseTransportsdisable broad route families before any specific route type is considered. - Shortest Path categories: vendored TSV rows are classified by source file, including agility/grapple shortcuts, boats, canoes, charter ships, ships, fairy rings, gnome gliders, hot air balloons, magic carpets, magic mushtrees, minecarts, quetzals, quetzal whistle, spirit trees, teleport item/box/lever/portal/POH/spell/home-spell/minigame rows, wilderness obelisks, seasonal transports, and generic transports.
avoidWilderness: Skips wilderness routes unless the destination itself is in the wilderness.withBankedRoutePlanning(true): asks the PacketUtils-owned Shortest Path plugin to select the complete bank-aware route. The result supplies the bank-transition path index and a concrete item-ID/quantity withdrawal manifest. Walker executes only the prefix through that bank, waits for bank-open and fresh container evidence, revalidates the manifest, withdraws throughBankActions, confirms carried quantities, and requests a fresh non-banked route. Dispatch alone never advances a preparation phase.
Bank contents are unknown until we observe a Bank container event after the current login. Known-empty and unknown are distinct. Login screen, hopping, connection loss, SDK unregister, and plugin shutdown clear the retained container state. Bank preparation is bounded and reports structured failure reasons through WalkerPath.getFailureReason(); cancellation also cancels the correlated planning request and prevents later withdrawal or walking.
PacketUtils applies the suite route policy for each Walker.planTo(...) and Walker.walkTo(...) call. The PacketUtils config maps the pinned Shortest Path settings, POH options, transport cost thresholds, currency threshold, and route display settings into the walker. Shortest Path requirements decide whether a configured route edge is usable.
There is no local Walker route registry or fallback planner. Shortest Path transport metadata is resolved into action-specific Walker executors only after the plugin has selected an exact edge.
The Path Handle (WalkerPath)
Packet Utils' disabled-by-default Use synthetic mouse option also governs Walker land movement. Disabled mode preserves the existing click-packet then MOVE_GAMECLICK sequence. Enabled mode projects the deterministic, door-safe lookahead onto the live canvas or minimap, tries nearer route tiles when necessary, moves the cursor into the selected shape, and installs an exact MenuAction.WALK / Walk here native click only after cursor arrival.
Synthetic movement is request-correlated and fail-closed. Walker waits while cursor travel or client-thread arrival dispatch is pending, and advances only after observing player movement, route progress, or arrival. Busy or unprojectable attempts use the existing bounded retry/replan behavior without direct packet fallback. Cancellation, replacement, replanning, terminal failure, logout, and Packet Utils shutdown suppress stale arrival clicks. Imported adjacent-barrier transition movement uses this same operation.
Deterministic canvas and minimap projection
LiveWalkerContext validates canvas polygons with ClickCoordinateResolver before selecting them. At ten tiles or fewer it prefers a usable canvas polygon and falls back to the minimap. Beyond ten tiles it prefers the minimap and falls back to a usable canvas polygon. If neither projection is usable, movement fails closed and enters the existing bounded retry/replan path. Projection selection has no random branch.
Static tests do not establish live native walking. Live acceptance still requires visible cursor travel before each click, no movement before arrival, successful route progress/final arrival, cancellation without a later click, and restored menu state in RuneLite.
Core Methods
canReach(WorldPoint)/canReach(NPC)/canReach(TileObject): Spatial reachability checks.pathTo(WorldPoint goal): Returns a local same-plane collision preview (never null); it is not proof that a global transport-aware route exists or does not exist.walkTo(WorldPoint goal): Directs the walker to path to a goal tile.walkNear(WorldPoint goal, int distance): Plans globally to the requested tile and completes on the target plane within the accepted Chebyshev radius.walkPath(List<WorldPoint> path): Directs the walker to execute a contiguous land route.cancelWalk(): Stops the shared walker and resets navigation states.clear(): Resets current path, goal, and reached distance variables.
Deterministic Land-Step Lookahead
Walker no longer uses Random.nextInt() for tile selection. The lookahead is now deterministic:
int baseLookahead = Math.min(MAX_LAND_LOOKAHEAD_TILES, Math.max(5, distanceToTarget / 2));
int stepIndex = Math.min(remainingPath.size() - 1, baseLookahead);
MAX_LAND_LOOKAHEAD_TILES caps long-path land hops at 25 path points before
door clipping is applied. That keeps the first movement request clickable on
long routes instead of targeting a distant final or midpoint tile.
Movement Progress Resynchronization
Walker records each accepted land movement as an active queued command (actionQueued)
and coordinates progress without unnecessary redispatching:
LiveWalkerContext.queueMovement()records the targeted tile inWalkerMovementResultWalkerPath.tickLandStep()marks accepted land movement asactionQueued- Ordinary progress toward the queued hop preserves the accepted command without redispatching and resets stall tracking while the player advances
- Reaching the queued hop consumes the completed route prefix and selects the next door-clipped lookahead; landing beyond it resynchronizes from the observed player position so Walker cannot issue a movement back toward a stale hop
- A stationary command is retried only after the bounded stall window. A second stalled window cancels the queued movement and requests a fresh global route
This prevents redundant movement commands while in flight and avoids walking back to an older expected hop when RuneLite lands the player a tile or two beyond that hop.
Bank Preparation Hardening
Bank preparation phase REFRESH_BANK now includes:
- Content-hash comparison:
refreshed.getBank().hashCode() != bankObservationBaseline.hashCode() - Wall-clock timeout:
MAX_BANK_PREP_TOTAL_MS = 30_000(30 seconds) - Tracks
bankPrepStartMsatWALK_TO_BANKentry - Aborts with
WalkerFailureReason.STALE_STATEif timeout exceeded
Transport-Aware Stall Multipliers
Stall thresholds adapt to transport type via WalkerAction.getStallMultiplier():
| Transport Type | Multiplier | Example |
|---|---|---|
| LAND | 1.0 | Walking |
| TRANSPORT (fairy ring, spirit tree, etc.) | 1.5 | Slow dial/animation |
| TELEPORT (item/spell) | 1.2 | Fast teleport |
| CANOE | 1.3 | Multi-phase |
| TOLL_GATE | 1.1 | Quick passage |
Cost-Adjusted Transport Selection
WalkerRouteOptions.transportCosts now propagates to the Shortest Path planner via WalkerRouteOptionOverrides. Cost overrides are sent through the plugin message protocol and applied to edge weights before A* expansion. Example:
WalkerRouteOptions options = WalkerRouteOptions.builder()
.withTransportCost(WalkerRouteCategory.FAIRY_RING, 50)
.withTransportCost(WalkerRouteCategory.SPIRIT_TREE, 20)
.build();
Setting fairy ring cost +100 will cause the walker to prefer walking routes when available.
NavigationActions tracks the exact WalkerPath handle it started. If the shared walker path belongs to another caller or has already terminated, stale NavigationActions goal/path state is cleared before reporting an active-walk block. walkTo and walkNear do not gate global Shortest Path planning on pathTo; this permits doors, stairs, cross-plane routes, and transports that the local preview cannot represent. currentPath() reads the owned asynchronous Walker display route after planning.
WorldPoint altar = new WorldPoint(3052, 3484, 0);
InteractionResult result = NavigationActions.walkNear(altar, 2);
if (result.failed()) {
log.debug("Navigation failed: {}", result.getMessage());
}
3. Walk Assistant Plugin
WalkAssistantPlugin is a QoL plugin that walks to destinations on demand from hotkeys or its dedicated sidebar panel.
- Config group:
n3walkassistant - Properties registration:
runelite-plugin.properties
Triggers
- Quest/Clue Destination (
questDestHotkey): Resolves targets throughWalkDestinationResolverand walks there. - Nearest Bank (
nearestBankHotkey): Coordinates withWalkBankResolverto rank plane-local banks. - Cancel (
cancelWalkHotkey): Stops the active Walk Assistant path. - Destination panel: Offers curated Banks and Cities plus metadata-driven Farming, Hunter, Slayer, Minigames, and Guilds catalogs. Farming, Hunter, and Slayer use cascading Type -> Location -> Destination selectors. Farming, Hunter, and Slayer rows are checked-in TSV projections of the corresponding Microbot walker enums; minigame and guild rows are maintained in the same schema and loaded as immutable
WalkDestinationEntryvalues at startup. - Home (POH): Resolves the configured house portal from the current
POH_HOUSE_LOCATIONvarbit and walks to the matching external portal tile. Unknown/unconfigured values fail closed.
The embedded Shortest Path UI owns world-map target selection. Set Target selects the route, starts the shared Walker when no other non-terminal path is active, and closes the world map after the walk is accepted; Start Path remains available for a single valid preview target. Manual world-map walking does not depend on Walk Assistant being enabled.
"Set Target" Menu Integration
WalkAssistantPlugin owns only its configured destination, nearest-bank, POH, hotkey, and cancellation requests. It does not observe or execute Shortest Path world-map menu actions.
Walk Assistant calls the shared Walker.walkTo(...) facade directly. PacketUtils owns suite-level route settings, ticking, and path overlay display.
PacketUtilsWalkerOverlay renders Walker.getActivePath() from PacketUtils display settings so all walker callers share one path-overlay owner. Walk Assistant additionally registers an ETA/status panel while it owns a non-terminal path; the ETA uses the remaining display path and the client run-toggle varp to choose walk/run rates. New panel or hotkey requests may replace a Walk Assistant-owned active path, but foreign active shared walker paths are blocked. Explicit Walk Assistant cancel stops any current non-terminal shared walker path and clears both Walk Assistant and NavigationActions tracking; it logs no active walk only when Walker.getActivePath() is null or terminal.
4. Movement Transport Actions & Data
The package com.n3plugins.Api.actions.movement contains transport action controllers and coordinate/ID data enums.
n3Plugins vendors Skretzo shortest-path resources. Metadata and license files are packaged under src/main/resources/com/n3plugins/sdk/walker/shortestpath/. The bulk data files (collision-map.zip, destinations/**, transports/**) are downloaded and cached at runtime by VendorResourceDownloader to save JAR space. The PacketUtils-owned Shortest Path plugin loads the modern Skretzo transport TSVs from the downloaded cache directory; the old single transports.txt bridge is not packaged.
collision-map.zip stores per-region raw flag bitsets inside the zip. The loader accepts those raw entries, plus older gzip/serialized region payloads. Do not GZIP-decompress a zip entry unless its bytes start with GZIP magic 1F 8B; otherwise live planning fails in SplitFlagMap with ZipException: Not in GZIP format.
The downloader uses the SHA and GitHub archive URL pinned in VendorResourceDownloader, writes to RuneLite.RUNELITE_DIR/n3Plugins/shortestpath/, and records a matching version marker after extraction. The route service and legacy shortest-path loaders read that cache before any classpath fallback. This keeps large, revision-bound data out of the plugin jar while allowing Packet Utils-owned walker startup to prepare it once per pinned upstream snapshot.
For a clean Cloud test environment, populate that same cache from the pinned archive before running walker or aggregate build tests; see the Linux bootstrap command in validation.md.
Imported route execution is type-aware. Complex networks dispatch to dedicated WalkerAction implementations. Ordinary NPC and object rows retain metadata-driven fallbacks, while generic TRANSPORT rows resolve their exact ID/name/action against live NPC and object targets and fail closed unless one target kind matches. NPC transports advance only allowlisted travel/payment dialogue and complete after observed arrival within five tiles of the edge destination. Static object transports wait for destination arrival rather than object despawn. An adjacent Open edge may move through a barrier already observed open, but missing non-barrier targets still fail closed. Multi-word menu actions are preserved, and agility or grapple shortcut transitions use a bounded wait before recovery. See Walker Transport Execution for the dispatch matrix, metadata contract, state transitions, and live acceptance checklist.
Run refresh and freshness checks by hand:
.\gradlew.bat updateShortestPathVendor --console plain
.\gradlew.bat checkShortestPathVendor --console plain
Use -PshortestPathSha=<commit> with updateShortestPathVendor to pin a specific upstream commit. Compile/test/build tasks skip GitHub.
Transport Action Classes
Public movement actions return InteractionResult and use shared pacing. Internal walker transport executors implement WalkerAction; they inspect result acceptance through WalkerContext and expose boolean progress to WalkerPath.
Fairy Rings (FairyRingActions)
travelTo(FairyRing destination): Travels to a fairy ring destination by code.dial(FairyRing code): Configures the ring dial wheels to code without traveling.currentCode(): Reads the active dial code.
InteractionResult result = FairyRingActions.travelTo(FairyRing.fromCode("BKR"));
Charter Ships (CharterShipActions)
travelTo(CharterShip port): Interacts with charter captains to sail to destination.
Gnome Gliders (GnomeGliderActions)
travelTo(GnomeGlider destination): Interacts with pilots to fly to destination.
Spirit Trees (SpiritTreeActions)
travelTo(SpiritTree destination): Interacts with spirit trees (usesWorldAreabounds checking).
Other Transports (TransportActions)
travelTo(MagicCarpet)/travelTo(Minecart)/travelTo(BirdFlight).
Coordinate Data Enums
FairyRing
47 entries. Fields: getCode(), getDestination(), getWorldPoint().
Lookup: FairyRing.fromCode(String code) (case-insensitive).
GnomeGlider
7 entries. Fields: getNpcId() (always 491), getDestination(), getWorldPoint().
CharterShip
9 entries. Fields: getNpcId() (always 1519), getPortName(), getWorldPoint().
SpiritTree
11 entries. Fields: getDestination(), getWorldArea().
MagicCarpet
7 entries. Fields: getNpcId() (always 1566), getLocation(), getWorldPoint().
Minecart
3 entries. Fields: getStation(), getWorldPoint() (Keldagrim, GE, Dwarven Mine underground).
BirdFlight
5 eagle destinations. Fields: getDestination(), getWorldPoint().
BankLocation
40 entries outlining bank boundary boxes. Fields: getWorldArea().
WorldPoint here = client.getLocalPlayer().getWorldLocation();
for (BankLocation bank : BankLocation.values()) {
if (bank.getWorldArea().contains(here)) {
log.debug("Currently inside bank: {}", bank.name());
break;
}
}
5. Requirements System
com.n3plugins.sdk.walker.requirements.QuestRequirement adapts RuneLite Quest and QuestState checks for QuestProgressApi. The PacketUtils-owned Shortest Path planner evaluates transport item, skill, variable, world, diary, and rune requirements from its parsed transport catalog. The shared Walker consumes the selected route and its concrete withdrawal manifest instead of maintaining a second requirement model.
Testing
For Walker execution tests, inject a fake WalkerContext; route-planning tests belong to the embedded Shortest Path pathfinder and plugin-message contract:
@Before
public void setUp() {
WalkerContextBridge.setAdapterForTesting(fakeContext);
ActionPacer.reset();
}
@After
public void tearDown() {
WalkerContextBridge.resetAdapterForTesting();
}
Run the focused Shortest Path pathfinder and vendor-resource tests when changing collision-map.zip, SplitFlagMap, FlagMap, transport reconstruction, or the vendor refresh task.