import type { NavPoint } from "../nav/navGrid.js"; import { type RandomSeed } from "../random/rng.js"; /** One species that ambiently populates a region: how heavily it is favoured and its alive ceiling. */ export interface PopulationSpeciesEntry { /** Opaque species/creature id the engine never interprets. */ species: string; /** Relative pick weight among species below their cap (default 1). */ weight?: number; /** Target/maximum number of this species alive in the region at once. */ cap: number; } /** Ambient population budget for a single region: its species ceilings, refill timing, and spawn geometry. */ export interface PopulationRegionConfig { /** Opaque region id the engine never interprets. */ region: string; species: readonly PopulationSpeciesEntry[]; /** Seconds a freed slot waits before it becomes a spawn candidate (overrides the director default). */ respawnDelay?: number; /** Candidate spawn locations; when present each request is tagged with one. */ spawnPoints?: readonly NavPoint[]; } /** Full ambient-population configuration: per-region species budgets plus global refill limits and PRNG seed. */ export interface PopulationDirectorConfig { regions: readonly PopulationRegionConfig[]; /** Default respawn delay in game-seconds for regions without their own (default 0 — refill on the next tick). */ respawnDelay?: number; /** Upper bound on spawn requests emitted per {@link PopulationDirector.tick} call (default 32). */ maxSpawnsPerTick?: number; /** Seed for the persisted, serializable PRNG cursor (default 1). */ seed?: number; } /** A single ambient spawn the game should realise, refilling a region toward its species caps. */ export interface PopulationSpawnRequest { region: string; species: string; /** Chosen spawn location when the region declares `spawnPoints`. */ point?: NavPoint; } /** Per-call spawn-point selection hook, letting a game apply bias (distance from players, etc.) at tick time. */ export interface PopulationTickContext { /** * Choose a spawn point for a request. Receives the region id, its configured candidates, and a * deterministic roll in `[0, 1)` drawn from the persisted cursor. Return `null` to leave the * request unplaced. When omitted, a candidate is picked uniformly from the roll. */ pickSpawnPoint?(region: string, candidates: readonly NavPoint[], roll: number): NavPoint | null; } /** Live census of alive counts keyed `region -> species -> count`, used to reconcile the director to reality. */ export type PopulationCensus = Record>; /** * A queued refill slot awaiting its {@link PopulationDirectorState.elapsed} deadline. * @internal */ export interface PopulationRespawnSlot { region: string; readyAt: number; } /** * Opaque, JSON-serializable persistence for a {@link PopulationDirector}. Round-trip it through * `snapshot`/`hydrate`; never read or mutate the fields directly. */ export interface PopulationDirectorState { /** Accumulated game-seconds. */ elapsed: number; /** Believed alive counts, `region -> species -> count`. */ alive: Record>; /** Pending refill slots, each ready at its `readyAt` game-second. */ queue: readonly PopulationRespawnSlot[]; /** Opaque PRNG cursor — round-trip as part of the state, never read or set it directly. */ rng: RandomSeed; } /** Stateful ambient population manager for one world; drive it with `tick` and lifecycle notifications. */ export interface PopulationDirector { /** * Advance by `dt` game-seconds and emit spawn requests for refill slots whose delay has elapsed, * up to the per-tick cap. Only touches the bounded respawn queue — never scans the world. */ tick(dt: number, ctx?: PopulationTickContext): PopulationSpawnRequest[]; /** Register that a creature left the region (killed/despawned/tamed), freeing a slot to refill after the delay. */ notifyRemoved(region: string, species: string, id?: string): void; /** Register a creature the game introduced itself (not via a request), so caps account for it. */ notifySpawned(region: string, species: string, id?: string): void; /** Snap believed alive counts to a live census and re-queue any deficit toward the caps. */ reconcile(census: PopulationCensus): void; /** Believed alive count for a `(region, species)` pair. */ alive(region: string, species: string): number; /** Serializable copy of the full director state (deep, safe to persist). */ snapshot(): PopulationDirectorState; /** Replace the director state from a previously taken (and possibly JSON round-tripped) snapshot. */ hydrate(state: PopulationDirectorState): void; } /** * Ambient open-world population director. Holds a target creature population per region and refills * it over time, distinct from wave/horde spawning: instead of a budget draining down a wave, each * region has per-species caps and a timed respawn queue. Removing a creature frees a slot; after a * respawn delay `tick` emits weighted spawn requests (bounded per tick) that refill toward the caps * without ever exceeding them. Species are chosen through a persisted, serializable {@link RandomSeed} * cursor, so the same seed and the same lifecycle events always yield the same requests. Regions are * independent, spawn points are optional, and the whole state round-trips through `snapshot`/`hydrate`. * * @capability population-director ambient per-region creature population manager with timed weighted respawns and caps */ export declare function createPopulationDirector(config: PopulationDirectorConfig): PopulationDirector;