npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rbxts/navigate

v0.1.3

Published

Composable TypeScript-native pathfinding suite for roblox-ts. Wraps PathfindingService with multi-agent coordination, formations, and arrival behaviors.

Readme

@rbxts/navigate

Composable, TypeScript-native pathfinding for roblox-ts. Wraps Roblox's PathfindingService with the things it doesn't give you out of the box: multi-agent coordination, formations, arrival behaviors, and scalable pursuit with a per-frame compute budget.

import { PathManager } from "@rbxts/navigate";

const manager = PathManager.create();
manager.registerAgent(zombieModel, { speed: 14, visualize: true });
manager.setTarget(zombieModel, player.Character.HumanoidRootPart);

That's it — the zombie pursues, recovers from getting stuck, transitions to direct steering when close, and never overlaps with ComputeAsync calls from other zombies.


Why

Roblox's built-in PathfindingService is a navmesh + a path object, and that's it. Anything past "one NPC walking somewhere" is on you:

  • Many NPCs at once → unbounded ComputeAsync calls per frame, frame drops.
  • A swarm chasing one player → every NPC pathfinds independently to the same target.
  • Close-range pursuit → the path lags behind the player; NPCs run to where you were.
  • Formations → not a thing.

navigate is the layer above. You opt into the parts you need: the single-agent wrapper, the multi-agent manager, or the group/formation system on top.


Install

bun add @rbxts/navigate
# or
npm install @rbxts/navigate

Layers

| Layer | What | When | |---|---|---| | PathAgent | Single-agent wrapper around PathfindingService. Waypoints, jumps, stuck detection, visualization. | One-off NPCs; total control. | | PathManager | Multi-agent coordinator (singleton). Two-phase pursuit, auto-clustering, per-frame compute budget. | Squads, swarms, anything > 1 agent. | | Groups + formations + arrival | Explicit named groups with wedge/line/circle/grid formations and converge/surround/stack/spread arrival. | Tactical AI. Encounters that need shape. |

You can mix: a PathManager with both ungrouped agents (auto-clustered) and explicit named groups.


Quick start

Single agent

import { PathAgent } from "@rbxts/navigate";

const agent = new PathAgent(npcModel, {
  agentParams: { AgentRadius: 2, AgentHeight: 5, AgentCanJump: true },
  speed: 16,
  visualize: true, // draws path waypoints
});

agent.run(targetPart); // or a Vector3

agent.reached.Connect((waypoint, partial) => {
  print("arrived (partial:", partial, ")");
});

agent.stuck.Connect(() => {
  print("can't make progress — try jumping or pick a new goal");
});

PathAgent works standalone — no PathManager required.

Many agents pursuing a player

import { PathManager } from "@rbxts/navigate";

const manager = PathManager.create();

for (const zombie of allZombies) {
  manager.registerAgent(zombie, {
    speed: 12,
    visualize: false,
    agentParams: { AgentRadius: 2, AgentHeight: 5, AgentCanJump: true },
  });
}

manager.setTargetAll(player.Character.HumanoidRootPart);

The manager handles everything from here:

  • Caps ComputeAsync calls per frame (default 20). Excess agents wait their turn.
  • Clusters nearby agents pursuing the same target — only the closest one to the target pathfinds; the rest follow it directly.
  • When an agent gets within ~20 studs of the target with line-of-sight, it switches to direct steering (Humanoid:MoveTo) for smooth close-range pursuit.

Groups, formations, and arrival

For coordinated movement (squads, escorts, encounter design), use named groups.

manager.createGroup("alpha-squad", [grunt1, grunt2, grunt3, grunt4], {
  formation: { formationType: "wedge", spacing: 6 },
  arrivalBehavior: { arrivalType: "surround", radius: 12 },
});

manager.setTarget(grunt1, playerRoot); // any member; group inherits

The group picks one pathfinder. Other members ride the formation: a wedge shape while moving, then redistribute to surround the target on arrival.

Formation types

| Type | Shape | |---|---| | none | All followers converge on the leader (no offset). | | line | Alternating left/right perpendicular to heading. | | wedge | V-shape spreading backward from the leader. | | circle | Even ring around the leader. | | grid | Square grid behind the leader. | | custom | You provide an offsets: Vector3[] array, leader-local. |

Set spacing to control density:

{ formationType: "line",   spacing: 4 }   // tight infantry line
{ formationType: "wedge",  spacing: 8 }   // loose V
{ formationType: "circle", spacing: 10 }  // ring around the leader
{ formationType: "grid",   spacing: 5 }   // square block behind

Or hand-place each member:

{
  formationType: "custom",
  spacing: 0, // unused for custom
  offsets: [
    new Vector3(-4, 0, -2),
    new Vector3( 4, 0, -2),
    new Vector3(-8, 0, -4),
    new Vector3( 8, 0, -4),
  ],
}

Custom offsets are in leader-local space (X = right, Z = forward) and get rotated to follow the leader's heading.

Arrival behaviors

When the group reaches its target, members redistribute:

| Type | Behavior | Use case | |---|---|---| | converge | All members move to the target position. | Default; pile-on attack. | | surround | Even ring around target at radius. | Encirclement, boss fights. | | stack | Line up behind the approach direction at stackDistance apart. | Single-file through a doorway. | | spread | Semicircle fan on the approach side at radius. | Firing line, "we have you cornered." |

manager.setArrivalBehavior("alpha-squad", {
  arrivalType: "surround",
  radius: 14,
});

The approach direction is cached the moment the group first enters CLOSE phase, so members don't oscillate as the target moves.


Configuration reference

Per-agent

manager.registerAgent(model, {
  // Pathfinding
  agentParams: { AgentRadius: 2, AgentHeight: 5, AgentCanJump: true },

  // Movement
  speed: 16,
  visualize: false,

  // Stuck detection
  timeoutEnabled: true,
  timeoutMultiplier: 2, // 2x estimated time before declared stuck

  // Pursuit tuning
  enablePrediction: false, // aim ahead of moving target via velocity
  priorityBias: 0,         // higher = compute budget sooner

  // Custom movement (for non-humanoid agents)
  onMove: (pos) => {/* drive a vehicle */},
  onJump: () => {/* … */},
});

Manager (global)

PathManager.create({
  computeBudgetPerFrame: 20,    // ComputeAsync calls/frame
  closeRangeThreshold: 20,      // FAR↔CLOSE switch distance
  dirtyDistanceThreshold: 10,   // target-moved distance that triggers recompute
  recomputeCooldown: 0.5,       // min seconds between recomputes per agent
  clusterRadius: 15,            // cluster join radius
  clusterDriftThreshold: 20,    // follower drift before leaving cluster
  updateInterval: 0.1,          // 10 Hz manager tick
});

Visualization

When visualize: true on an agent, the manager paints small neon parts at each waypoint:

| Color | Meaning | |---|---| | 🟢 Green | This agent is its own pathfinder (cluster leader / ungrouped). | | 🟠 Orange | Group pathfinder waypoints. | | 🔵 Cyan | Follower (no compute — riding leader's path). | | 🟡 Yellow | Jump waypoint. | | 🔴 Red | Final destination. Also: CLOSE-phase target indicator. |

Stale waypoints persist across phase transitions until a new path replaces them — useful for debugging.

For runtime stats (agent counts, leader/follower counts, FAR/CLOSE/idle splits), call:

const stats = manager.getStats();
//   total, far, close, idle, leaders, followers, clusters, groups

Wire that into your own debug HUD however you like.


Pursuit model

The manager runs each agent through three pursuit states:

stateDiagram-v2
    [*] --> IDLE
    IDLE --> FAR: target set
    FAR --> CLOSE: in range AND has LOS
    CLOSE --> FAR: out of range OR LOS broken
    FAR --> IDLE: target lost
    CLOSE --> IDLE: target lost

    note right of IDLE
        no target, no movement
    end note
    note right of FAR
        PathfindingService
        compute is rate-limited
    end note
    note right of CLOSE
        direct Humanoid:MoveTo
        cheaper, smoother
    end note
  • FAR = use PathfindingService for full nav-mesh paths. Compute is rate-limited.
  • CLOSE = direct Humanoid:MoveTo toward the target. Smoother and cheaper, but only when the path is straight (LOS check guards against walking into walls / off ledges).
  • IDLE = no target, no movement.

Auto-clustering kicks in during FAR: ungrouped agents pursuing the same target form clusters by spatial proximity (with line-of-sight required between follower and leader). Followers MoveTo(leader.position) instead of running their own ComputeAsync. One pathfinder, N followers, cheap as chips.


Working with signals

PathAgent exposes Roblox-style signals you can connect to:

const agent = manager.registerAgent(npc, { speed: 16 }).pathAgent;

agent.reached.Connect((waypoint, partial) => { /* arrived */ });
agent.waypointReached.Connect((current, upcoming) => { /* per-step */ });
agent.blocked.Connect((waypointIdx) => { /* obstruction ahead */ });
agent.stuck.Connect(() => { /* timeout — try jumping or repath */ });
agent.error.Connect((err) => { /* "no_path" | "timeout" | etc. */ });
agent.statusChanged.Connect((newStatus, oldStatus) => { /* … */ });
agent.stopped.Connect(() => { /* explicit stop() called */ });

PathManager emits one signal:

manager.phaseChanged.Connect((agent, newPhase, oldPhase) => {
  // useful for triggering attack animations on FAR → CLOSE
});

Cleanup

PathManager owns the RunService.Heartbeat connection. Tear it down explicitly when you're done:

manager.unregisterAgent(npcModel); // single
manager.destroy();                 // entire manager

Per-agent PathAgent instances created without a manager:

agent.destroy();

License

MIT — © 2026 Stephen Horton.