Skip to content

[FEATURE] AI seams: polygon navmesh, perception service, decision graph #1684

Description

@Noisemaker111

Problem: the only agent brain is a five-state enum (packages/core/src/ai/mobBrain.ts:4), behaviors are four descriptors (packages/core/src/scene/behaviors.ts), perception is a cone test (packages/core/src/sensor/visionCone.ts), navigation is an XZ grid A* (packages/core/src/nav/navGrid.ts). Verified on main.

Tasks, one PR each, in order. Everything here is pure core with tests; the probe at the end needs a clip.

  1. Perception service. New packages/core/src/sensor/perception.ts: createPerception({ sightRange, sightConeDeg, hearingRange, memorySeconds, occluded?: (from, to) => boolean }) with pushStimulus({ kind: "sight" | "sound" | "damage"; sourceId; position; loudness?; at }), observe(observer: { id; position; yaw }, candidates: { id; position }[], nowMs) that updates memory, memory(observerId) -> { targetId; lastSeenAt; lastKnownPos; confidence }[], retune, snapshot, restore. Confidence decays linearly over memorySeconds. Tests: seen target remembered after leaving the cone, sound stimulus creates memory without sight, occluder blocks sight. bun run gen. CHANGELOG Added.

  2. Decision graph. New packages/core/src/ai/decisionGraph.ts: DecisionNode = { kind: "selector"; children } | { kind: "sequence"; children } | { kind: "condition"; key; op; value } | { kind: "action"; action: string; params? } | { kind: "utility"; options: { score: { key; weight }[]; node }[] }; createDecisionGraphRuntime(graph, actions: Record<string, (ctx, params, blackboard) => "running" | "done" | "failed">) with tick(ctx, blackboard, dt), snapshot, restore. Blackboard is a Record<string, number | boolean | string>. Tests: selector falls through, sequence stops on failure, utility picks the highest score, running action resumes next tick. Then re-express mobBrain as one graph in packages/core/src/ai/mobBrainGraph.ts and add a test proving the same decisions as mobBrain.test.ts on the same inputs.

  3. Behavior descriptor. Add { kind: "decisionGraph"; graph: DecisionGraph; actions: string } (actions registered by name through a small registry registerBehaviorActions(id, actions)) to the union in packages/core/src/scene/behaviors.ts and advance it in packages/core/src/scene/behaviorRuntime.ts at interestScheduler cadence. Test: an entity with the descriptor moves toward a remembered position.

  4. Polygon navmesh. New packages/core/src/nav/navMesh.ts: NavMeshData { verts: number[]; polys: number[][]; links: { from; to; cost? }[]; areas?: number[] }, buildNavAdjacency, findPath(mesh, from, to) -> { points: [x,y,z][]; polys: number[] } (A* over polygon centers then funnel/string-pull), closestPoint, raycastNav. Tests on a hand-built L-shaped mesh and a mesh with an off-mesh link. Keep navGrid untouched.

  5. Navmesh bake. New package packages/navbake/ (layout like packages/sql/), depending on recast-navigation (npm recast-navigation, pin latest): bakeNavMesh({ positions, indices, agentRadius, agentHeight, maxSlope, maxClimb }) -> NavMeshData. Add bakes?: { kind: "nav" | "minimap"; id; data }[] to the editor document in packages/core/src/editor/types.ts (migrate the existing minimap bake to it in a compatible way), an editor command bakeNavMesh in packages/editor/src/handlers/, and an MCP tool. Register the package everywhere (build chain, export manifest, README table, tarball test). Test: baking a floor with a wall produces a path that goes around the wall.

  6. Tactical queries. New packages/core/src/ai/tacticalQueries.ts: coverPoints(mesh, threatPos, losBlocked: (a, b) => boolean) (navmesh boundary edge midpoints with blocked line of sight to the threat), scorePositions(points, { distanceTo, flankOf, retreatFrom }, weights) bounded by a maxCandidates. Tests.

  7. Probe and clip. A guard in apps/dev or examples/: patrols, hears the player (sound stimulus from movement), investigates the last known position, loses the player, returns. bun run pr-video clip. Closes #1684.

Rules: wait for bun run agent:bootstrap --check; branch claude/<slug> off origin/main; claim comment first; one task per PR with Refs #1684; bun run gen after export changes (JSDoc on every export; create* factories need snapshot/restore); CHANGELOG bullet; check-types and test on touched packages plus check-stateful-ratchet, check-doc-symbols, check-orphan-ratchet; use ctx.rng, never Math.random; merge origin/main before pushing; squash auto-merge; fix CI on the same branch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions