The pack
navMesh
function navMesh(): Pack<{ navMesh: NavMeshApi }>
The nav-mesh pack: recast/Detour navmeshes and crowds. It needs no world: the meshes are pure computation over the
geometry it is given, in x, y (up) and z. The core is a lazy WASM
chunk; nothing is fetched until the first bake(). Headless it bakes fine — recast is pure computation.
Functions
bakeConfig
function bakeConfig(o: NavMeshOptions): { cs: number; ch: number; walkableSlopeAngle: number; walkableHeight: number; walkableClimb: number; walkableRadius: number }
Turn the world-unit options into recast's config, whose walkable* dials are in VOXELS.
buildNavMesh
async function buildNavMesh(opts: NavMeshOptions): Promise<NavMesh3d>
Bake a navmesh from geometry, loading the vendored core on first use.
terrainMesh
function terrainMesh( heightAt: (x: number, z: number) => number, size: number, res = 64, cx = 0, cz = 0, ): { positions: Float32Array; indices: Uint32Array }
Triangulate a heightfield region into a navmesh source mesh, wound CCW seen from above.
The winding is the whole trap: recast decides what is walkable from the face NORMAL, so a mesh wound the other way bakes to nothing at all and the error reads as "no walkable polygons" rather than "your triangles are inside out".
Classes
Crowd3d
class Crowd3d
recast's Crowd: agents that avoid each other locally. For dense groups that must not interpenetrate.
An agent is named by the number add returns. The crowd moves it; you read it back with position and velocity.
A number belongs to its agent until remove, after which recast gives it to the next agent added.
readonly maxAgents: number— The most agents this crowd holds at once.constructor(core: NavCore, navMesh: unknown, opts: CrowdOptions, snap: (p: NavVec3) => NavVec3 | null)add(pos: NavVec3, params: CrowdAgentParams = {}): number— Add an agent at a world position and return its number. The position is moved onto the mesh first, asnearestdoes: recast leaves an agent placed off the mesh (well above it, say) standing motionless and says nothing.paramsoverrides the defaults:radius0.6,height2,maxSpeed6,maxAcceleration20. Throws when the point is not near the mesh, or when the crowd already holdsmaxAgents.goto(idx: number, goal: NavVec3): boolean— Send an agent toward a world goal, moved onto the mesh first (recast alone refuses a goal more than a unit off the surface). Returns false, and leaves the agent as it was, when the goal is not near the mesh. The agent walks there over the following steps; it does not arrive at once.stop(idx: number): void— Drop an agent's goal. It brakes to a halt and stays where it stops, still avoiding the others.remove(idx: number): void— Take an agent out of the crowd, freeing its place foradd.position(idx: number): NavVec3— Where an agent is now, in world units.velocity(idx: number): NavVec3— How fast and which way an agent is moving, world units a second.get count(): number— How many agents it holds.update(dt: number): void— Step the crowd bydtseconds. The owning navmesh does this on every simulation step.free(): void
NavMesh3d
class NavMesh3d
A baked navmesh and its query. It satisfies the same path(start, goal) contract nav's NavGrid does, so
it drops straight into navigate — which is the whole point of having two tiers rather than two APIs.
readonly raw: unknown— The recast mesh, for anything this wrapper does not cover.dead: boolean— True afterkill(). A dead navmesh has freed its memory and its crowds': it stops ticking, and using it throws.constructor(core: NavCore, navMesh: unknown)randomPoint(): NavVec3 | null— A uniformly random point ON the mesh — a wander or patrol goal that is guaranteed reachable. Null if the mesh is empty.nearest(p: NavVec3): NavVec3 | null— Snap a point to the nearest walkable surface, or null when it is off the mesh entirely.path(start: NavVec3, goal: NavVec3, opts: { partial?: boolean; radius?: number } = {}): NavVec3[]— A route start→goal as world waypoints; empty when there is none. ThePathPlannercontract. A goal the mesh cannot reach fromstart(across a gap, behind a wall with no door) has no route; withpartial: truethe route goes as near to it as the mesh allows instead, which is what Detour itself answers.radiusis accepted and ignored, as the contract allows: the mesh keeps the clearance it was baked with.crowd(opts: CrowdOptions = {}): Crowd3d— A recast Crowd over this mesh, for dense local avoidance; the mesh steps it every simulation step. A game can make as many as it likes, each with its ownmaxAgents. Most games usesteeringinstead.tick(dt: number): voidkill(): void— Free the mesh, its query and its crowds' WebAssembly memory, stop ticking, and let the pack drop it. Using it, or one of its crowds, afterwards throws.
Types
CrowdAgentParams
type CrowdAgentParams = Partial<Record<'radius' | 'height' | 'maxSpeed' | 'maxAcceleration' | 'collisionQueryRange' | 'pathOptimizationRange' | 'separationWeight' | 'updateFlags' | 'obstacleAvoidanceType' | 'queryFilterType', number>>
The recast numbers an agent can be given. Anything unset takes recast's default, apart from the four this wrapper sets (below).
CrowdOptions
interface CrowdOptions
What a crowd takes to make: maxAgents is a hard cap (recast sizes the crowd up front); maxAgentRadius the widest agent it will hold.
maxAgents?: number— The most agents the crowd holds at once. Default 256.addthrows past it;removefrees a place.maxAgentRadius?: number— The widest agent radius the crowd is built for, world units. Default 0.6. Recast sizes its neighbour grid from it, so set it to the largestradiusyou willadd.
NavCore
type NavCore = Record<string, unknown> & { NavMeshQuery: new (mesh: unknown) => NavQuery; Crowd: new (mesh: unknown, o: object) => RawCrowd; generateSoloNavMesh(p: ArrayLike<number>, i: ArrayLike<number>, cfg: object): { success: boolean; error?: string; navMesh: unknown } }
The core module's surface is the vendored build's own; this wrapper is the contract, not the types.
NavMeshApi
interface NavMeshApi
game.navMesh.
bake(opts: NavMeshOptions): Promise<NavMesh3d>— Bake a navmesh from geometry, or from a heightfield. Resolves once the core has loaded and recast has run — it is a job, not a getter, so bake at load time rather than mid-level.readonly meshes: ReadonlyArray<NavMesh3d>— Every live navmesh; their crowds are ticked each step.readonly last: NavMesh3d | null— The last one baked, for a game that only needs one.terrainMesh(heightAt: (x: number, z: number) => number, size: number, res?: number, cx?: number, cz?: number): { positions: Float32Array; indices: Uint32Array }— Triangulate a heightfield region into a CCW-up source mesh, without baking.
NavMeshOptions
interface NavMeshOptions
What game.navMesh.bake() takes: explicit geometry, or a heightfield to triangulate, plus the recast bake dials.
positions?: ArrayLike<number>— Flat vertex xyz triples. Wind the triangles CCW seen from above, or the walkable faces point down and recast finds nothing.indices?: ArrayLike<number>— Triangle indices intopositions, three a triangle.from?: 'terrain'— …or triangulate a heightfield into a grid mesh instead.heightAt?: (x: number, z: number) => number— The height sampler forfrom: 'terrain'.size?: number— The side of the region to triangulate, world units. Required withfrom: 'terrain'.center?: { x: number; z: number }— The region's world centre. Default the origin.res?: number— Samples along each side. Default 64.cellSize?: number— Voxel cell size — the navmesh's resolution. Default 0.3.cellHeight?: number— Voxel cell height. Default 0.2.agentRadius?: number— The mesh is ERODED by this, so a route keeps clearance from walls. Default 0.6.agentHeight?: number— Minimum head-room. Default 2.agentMaxClimb?: number— The tallest step or ledge the agent can climb. Default 0.9.agentMaxSlope?: number— The steepest slope that counts as walkable, degrees. Default 50.module?: string— Override the core module URL in a development build, on the game's own origin; another origin throws. Rarely needed.
NavVec3
interface NavVec3
A point in the world. The same shape nav uses, so a planner drops into either.
x: number— The X position, in world units.y: number— The height, in world units.z: number— The Z position, in world units.


