Log inGet started
Package

pathfinding.package

Newest version

This package holds more than this page lists. See all of its files in its world.

README.md

pathfinding.module

Public API for the pathfinding package. Caller-facing entry point lives in init.luau; sibling modules implement individual concerns.

Modules

FilePurpose
init.luauPublic surface (path, addLink, addModifier, registries, costs).
astar.luauGeneric A* over an adjacent(node) -> { Edge } callback. Binary-heap frontier, opaque node identity via nodeKey.
graph.luauTwo concrete graphs: grid (uniform 3D voxel grid) and waypoint (hand-authored network). Both expose the same interface.
modifiers.luauVolumetric cost-zone registry. AABB containment, OBB hooks reserved for future engine OBB primitive.
links.luauOff-mesh link registry + per-query baking onto a graph.
path.luauWaypoint / Path constructors. Mirrors Roblox PathWaypoint shape.

Algorithm at a glance

  1. nodeAt(start) / nodeAt(goal) — snap world points onto graph nodes.
  2. links.bake(graph, agent) — snap every enabled, condition-satisfying link's endpoints onto graph node-keys; bucket adjacency lookups by from-key. One pass per path call, not per A* expansion.
  3. A* expands each node, calls the closure adjacent(node). The closure:
    • asks the graph for native neighbours,
    • for each neighbour, queries modifiers.costAt(midpoint, costs, mode),
    • skips edges with math.huge multipliers,
    • appends link-overlay edges from linkBuckets[nodeKey].
  4. On reach: Path.fromAStar(result, graph, edgeMeta) produces a list of {position, action, label, link, payload} waypoints. Walk edges become action = "Walk"; link edges become action = "Custom" with label = link.label.

Edge-cost composition

Per edge e: a -> b:

edge_cost = base_cost(a, b) * compose({ modifier.cost-at-mid(a, b) }, mode)

Composition mode:

  • multiply: cost *= Π c_i (default; composable preferences amplify).
  • max: cost *= max(c_i) (worst-zone wins; conservative).
  • first: cost *= c_first (registration order; legacy).

math.huge from any modifier blocks the edge regardless of mode. The per-edge midpoint sample matches Roblox's PathfindingModifier semantics exactly — multiple overlapping modifiers compose.

Identity & determinism

  • A* node identity is by string key (graph.nodeKey(node)), not table identity, so transient nodes built by nodeAt snap onto the same key as graph-internal nodes.
  • The frontier is a binary min-heap; ties on f are broken by insertion order. With identical inputs the same path is returned — important for tests and replay.

Extension points

  • graph.luau exposes a clear interface (nodeAt, nodeKey, position, adjacent, heuristic, edgeMidpoint). Future navmesh-polygon graphs implement the same shape; nothing in the rest of the package cares.
  • modifiers.luau already exposes a basis field for oriented bounding boxes; the AABB-containment fast path is the current default. When an OBB primitive lands (#2677-style attachment basis or a future renderer-side OBB) the basis-aware branch in modifiers.contains is the single place to extend.
  • links.luau::bake builds the per-query overlay; if predicate links start showing up in profiles, this is the cache point.

Boundaries

  • This module performs no engine I/O: no physics raycasts, no entity spawning, no renderer state, no time. Pure data in, path out.
  • That means the package never breaks the engine, and tests can exercise it without booting the engine — see astar.luau::search
    • graph.grid for the canonical setup.
  • The components (PathfindingLink.component, PathfindingModifier.component) own the entity-side wiring and call _addLinkFromComponent / _addModifierFromComponent in awake. They're a thin, optional adapter on top of the module.