Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

readme.md

pathfinding system

Generic, content-only pathfinding library for the Zero engine. A* over a caller-provided graph, with volumetric cost zones (PathfindingModifier) and off-mesh links (PathfindingLink) composed in.

The package is intentionally engine-light: it never reaches into renderer state, never assumes a baked navmesh exists, and never depends on unimplemented primitives. Instead it accepts whatever graph the caller supplies (grid, navmesh polygons, hand-authored waypoint network) and runs A* with modifier/link composition on top.

This addresses two issues that previously had no shared substrate:

  • #2714 PathfindingLink — off-mesh link components (ladders, ziplines, jumps, teleporters) with bidirectional, predicate, and label-cost semantics matching Roblox's PathfindingLink.
  • #2715 PathfindingModifier — volumetric cost zones (lava, roads, crosswalks, fog) with multiplicative, max, or first-wins composition.

Layout

systems/pathfinding.package/
  pathfinding.module/            -- public API (require this)
    init.luau                    -- Pathfinding.path / addLink / addModifier / ...
    astar.luau                   -- generic A* over an adjacency callback
    graph.luau                   -- GridGraph helper (3D grid -> graph adapter)
    modifiers.luau               -- volumetric modifier registry + cost lookup
    links.luau                   -- off-mesh link registry + adjacency overlay
    path.luau                    -- Path / PathWaypoint constructors
    README.md                    -- design reference

  PathfindingLink.component/     -- attach to entities to register a link
  PathfindingModifier.component/ -- attach to entities to register a cost zone

  demo/
    pathfinding_demo.scene/      -- showcase: lava + road + crosswalk + ladder

Quick start

local Pathfinding = require("@builtin::systems.pathfinding.pathfinding")

-- Register a cost zone programmatically (or attach a PathfindingModifier
-- component to any entity for the same effect).
local lavaId = Pathfinding.addModifier({
    aabb  = { min = {-5, -1, -5}, max = {5, 1, 5} },
    label = "Danger",
})

-- Register an off-mesh link (or attach a PathfindingLink component).
local ladderId = Pathfinding.addLink({
    from  = {0, 0, 0},
    to    = {0, 4, 0},
    label = "ClimbLadder",
    cost  = 5.0,
    isBidirectional = true,
})

-- Build a 2D grid graph (32x32 cells of 1 metre each) from a walkability
-- predicate. The predicate is called once per cell during build.
local grid = Pathfinding.gridGraph({
    origin = {0, 0, 0},
    size   = {32, 1, 32},
    voxelSize = 1.0,
    walkable = function(x, y, z) return true end,
})

-- Compute a path. The agent's `costs` table is consulted for every
-- modifier whose AABB contains an edge midpoint.
local path = Pathfinding.path({
    graph = grid,
    start = {0, 0, 0},
    goal  = {20, 0, 20},
    agent = {
        radius = 0.4,
        canSwim = false,
        costs = {
            Danger    = math.huge,
            Road      = 0.5,
            Crosswalk = 0.01,
            ClimbLadder = 1.0,
        },
    },
})

for i, wp in ipairs(path.waypoints) do
    -- wp.position = { x, y, z }
    -- wp.action   = "Walk" | "Custom"
    -- wp.label    = link.label (when action = "Custom")
    print(i, wp.action, wp.label or "-", wp.position.x, wp.position.y, wp.position.z)
end

Conflict resolution

When multiple modifiers overlap, costs combine per Pathfinding.modifierConflict:

  • "multiply" (default) — cost = base * c1 * c2 * .... Composable; a road overlaid with a crosswalk amplifies the road's preference.
  • "max" — cost = base * max(c1, c2, ...). Conservative; the worst zone wins. Useful for danger-overlaps-road semantics.
  • "first" — cost = base * c_first. Legacy mode; whichever modifier was registered first wins.

math.huge from any modifier blocks the edge regardless of mode.

Predicate links (Zero leapfrog)

Pathfinding.addLink({
    from = {0, 0, 0}, to = {10, 0, 0},
    label = "SwimAcross",
    condition = function(agent)
        return agent.canSwim and not _G.world.isStormy
    end,
})

The predicate runs once per Pathfinding.path call when the link's cost is queried, so dynamic gating doesn't require manual enabled toggling.

Documentation

  • pathfinding.module/README.md — algorithm + module-by-module reference
  • PathfindingLink.component/ — component schema
  • PathfindingModifier.component/ — component schema
  • demo/pathfinding_demo.scene/ — runnable showcase

Status vs the open pathfinding constellation

This PR sits in the middle of an 18-issue pathfinding stack. What this package does and does not cover:

Closes

  • Closes #2714 — PathfindingLink off-mesh component.
  • Closes #2715 — PathfindingModifier volumetric cost-zone component.

Addresses (Luau-side; Rust binding still pending)

  • #2710 — pathfinding.* namespace. This package matches the documented surface (path/registerMaterialCost/findNearestPoint/ lineOfSight). The issue specifically asks for a Rust binding in crates/zero_scripting/src/ffi/bindings/pathfinding.rs; until that lands callers can require("@builtin::systems.pathfinding.pathfinding") or call Pathfinding.installGlobal() once at world boot to publish the table to _G.pathfinding. The Rust binding will shadow it cleanly when shipped.
  • #2712 — Path object with :computeAsync/:waypoints/:setTarget/ .status/.blocked event. Implemented as a Luau OO type with the same shape as the planned binding.
  • #2713 — PathWaypoint with action ∈ {Walk, Jump, Custom} + label dispatch. Custom is emitted today by off-mesh links; Jump is carried through the type so future graphs (#2711) can emit it without API churn.
  • #2716 — material-based costs. registerMaterialCost / getMaterialCost / materialCosts use the same registry as modifier labels, so per-Path agent overrides shadow either uniformly.
  • #2722 (partial) — 3D agent profiles. gridGraph { connectivity = 26 } is full 3D + diagonals; connectivity = 6 is 3D axis-aligned. Climbing is handled by off-mesh links today rather than vertical navmesh polys.

Out of scope (separately filed, not touched here)

  • #2711 Recast-style navmesh bake (Rust).
  • #2717 NavMeshAgent component with autopilot (depends on characterController:moveTo, #2725).
  • #2718 ORCA/RVO local avoidance + crowd manager.
  • #2719 time-sliced / progressive path computation.
  • #2720 hierarchical pathfinding (HPA*).
  • #2721 streaming-aware navmesh tile lifecycle.
  • #2724 debug visualization + navmesh editor (depends on Visualization Options popup, #2643). Pathfinding.diagnostics.* is plumbed as a no-op hook so the future overlay slots in without touching call sites.
  • #2725 characterController:moveTo and friends.

The package is intentionally engine-light. When the Rust substrate (#1400) and Recast bake (#2711) ship, the graph adapter and per-edge cost composition are the natural seams to swap; the cost-zone, link, Path object, and waypoint-action types stay put.