import type { Vec3 } from "../world/geometry.js"; import { type NavCrowd, type NavCrowdOptions, type NavCrowdSnapshot } from "../nav/navCrowd.js"; import type { NavRuntime } from "../nav/navRuntime.js"; import { type UtilityBrainSnapshot, type UtilityCandidate, type UtilityInterrupt, type UtilityOption } from "../ai/utilityBrain.js"; import { type NeedDefinition, type NeedSnapshot } from "./needs.js"; import type { NearAgents } from "./offScreen.js"; /** Serializable state of one near agent: where it walks, what it needs, what it last chose. */ export interface NearAgentSnapshot { id: string; position: Vec3; goal: Vec3; place: string | null; activity: string | null; travelling: boolean; needs: NeedSnapshot; brain: UtilityBrainSnapshot; } /** Everything a near agent is doing, for a HUD, a probe or a test. Allocates. */ export interface NearAgentView { id: string; position: Vec3; goal: Vec3; place: string | null; activity: string | null; travelling: boolean; /** The utility pick for this tick; `null` before the first decision or with no candidates. */ pick: string | null; needs: NeedSnapshot; } /** What the near side needs from the game: a nav mesh, the shared need definitions, and what agents can do. */ export interface NearAgentsOptions { nav: NavRuntime; /** Need definitions shared with the off-screen world, so a far decay and a near tick agree. */ needs: readonly NeedDefinition[]; /** Utility options each brain scores; omit for agents that only carry needs and walk. */ options?: readonly UtilityOption[]; interrupts?: readonly UtilityInterrupt[]; /** Candidates the agent may pick this tick (where it stands, or anything the game offers). */ candidates?: (agent: NearAgentView) => readonly UtilityCandidate[]; crowd?: NavCrowdOptions; /** Register a devtools probe under this name so the overlay shows near-agent decisions. */ devtoolsName?: string; } /** * The near half of the off-screen world: one nav crowd for movement, one need set and utility brain * per agent for what it wants. Resident identity survives the handoff because the off-screen world * spawns and despawns through this seam and keeps the saved state. */ export interface NearCrowdAgents extends NearAgents { /** Utility decisions taken since creation — near agents run brains, far ones do not exist here. */ decisions(): number; view(id: string): NearAgentView | null; crowd(): NavCrowd; /** * Serializable state: each agent's needs and held pick, plus the crowd's own routes. The crowd is * part of the snapshot because a restored agent must resume the route it was walking, not re-plan a * fresh one from wherever it stood. */ snapshot(): { agents: NearAgentSnapshot[]; crowd: NavCrowdSnapshot; }; restore(next: { agents: readonly NearAgentSnapshot[]; crowd: NavCrowdSnapshot; }): void; } /** * Create the near-side simulation: agents on a nav mesh with decaying needs and a utility brain each. * The off-screen world decides which agents exist here, so a resident beyond its radius costs this * seam nothing at all — no path, no need tick, no decision. * * @capability near-agents nav crowd plus per-agent needs and utility brains for on-screen residents */ export declare function createNearAgents(options: NearAgentsOptions): NearCrowdAgents;