engine/enemies

PURPOSE — Runtime ownership of every hostile NPC in the world: chooses what spawns, where it spawns and how fast, ticks each living enemy’s movement / aggro / combat state machines, runs the AI Director’s pressure / mood / personality model, drives the boss-encounter pressure-add waves, and bridges the precomputed level zone grid into per-position spawn pools.

OWNS

  • Every live enemy entity in world.enemies (position, velocity, heading, HP, archetype, behavior id, personality, tag set, affix list, ability list, pack-leader back-reference, racer lane reference, status-effect map, boss/anchor/gate metadata, per-behavior FSM scratch fields).
  • The spawner’s running state: total-spawned counter, active-enemy count, delayed spawn queue, per-run quantity/quality curves and randomization, biome and enemy-set ids, forced-type dev override, attached level data, zone spawn-rate multiplier, recent-damage accumulator and timer, trickle accumulator, wave timer / count / telegraph latch, flank and retreat timers, elite-pack timer, rolling player-position samples.
  • The enemy recycle bin used to avoid allocations during sustained combat, plus the per-spawn SFX throttle.
  • The Director’s personality state, heat scalar, pressure scalar and component breakdown (survival / density / projectile / momentum), dynamic enemy cap, force-retreat and suppress-firing flags, and the smoothed mood vector (tension, chaos, dread, triumph, calm, urgency, heat-feel, death-proximity, combat-density, reward-moment).
  • The Director’s Knobs tuning block (fire-rate / aggro-range / world HP / world count multipliers, force-retreat and suppress-firing toggles).
  • The boss spawn-profile runtime’s wave bookkeeping (recurring fire counts, fired phase-index sets, one-shot fired-wave set) keyed by stable wave id.
  • The behavior registry (string id → handler object with movement / combat / spawn / damage / death / stun / render hooks and capability flags) and the registered handlers for the engine’s archetype set.
  • Racer lane geometry: the array of waypoint sequences derived from clustered terrain, with cardinal-direction fallback lanes when terrain is sparse.
  • Shared movement / steering / facing helpers (point-seek, car-like steer toward/away, dumb-mode face, mid-attack detector, terrain bounce, terrain pathfind) and the attack-FSM timing constants exposed for behavior modules.

READS FROM

  • data/enemies for enemy type definitions, archetype base stats, rarity tints and stat multipliers, hull-shape map, personality profiles, set-specific spawn pools, elite-pack picker, and the set swarm archetype.
  • data/weapons for enemy weapon stat blocks used by combat ticks.
  • data/planet-config for per-planet spawn grace seconds.
  • data/spawn-profiles for boss-encounter wave definitions, trigger kinds, and position selectors.
  • data/level-config for zone types and per-zone spawn pool / rate config.
  • engine/core state (shared game, ship), types, clock, and the frame-cached alive-enemy count.
  • engine/world/generation for the world generator surface used during spawn placement.
  • engine/world/chunk-manager for the per-level LevelData package (zone grid, hub/spoke layout, illumination map, level config).
  • engine/world/zone-classifier to look up the zone type at any world position.
  • engine/affixes/roll for elite affix rolls applied to spawned packs.

PUSHES TO

  • world.enemies — every spawn, every per-frame integration, every death-driven removal.
  • engine/combat via damagePlayer when enemy attacks land.
  • engine/audio via MicroSfx for spawn whooshes.
  • engine/world/wave-telegraph via fireWaveTelegraph when an upcoming wave projects above the big-wave threshold.
  • core/state (game) read-write for kill cap, ramp inputs, overtime, boss spawner gate, boss arena / profile / encounter timer, mission timer, and tier flags consumed by Director / spawner logic.

DOES NOT

  • Define enemy stats, archetypes, pools, rarity curves, or boss spawn-wave content — those are data and live in data/enemies, data/weapons, data/spawn-profiles, and data/level-config.
  • Compute the world terrain layout, the zone grid, or the illumination map — only consumes them via LevelData.
  • Detect projectile / contact hits against enemies or resolve damage math, affix triggers, knockback, status application, life steal, shield interactions, or boss-bar accounting — collisions, damage flow, and status maintenance live in engine/combat, engine/affixes, and the enemy-status module.
  • Render enemies, their telegraphs, HP bars, or any HUD element — only writes the state renderers read.
  • Award XP, advance kill streaks, fire kill telemetry, drop loot, or advance mission progress — death cleanup happens elsewhere; this module flips alive = false, decrements the active count, and returns the slot to the recycle bin.
  • Drive boss-bar HP, gate-room logic, or encounter end conditions — only spawns pressure-add enemies on profile triggers; boss bookkeeping lives in the bridge / boss-room flow.
  • Author or roll affixes / abilities — only attaches resolved affix and ability descriptors that the affix system rolls.
  • Pick the player’s auto-aim target or read player input.
  • Persist any state across runs — every field clears on Director.reset, GameMaster.reset, and resetBossSpawnProfile.

Signals fired / Signals watched — none. The module talks to other systems by direct calls (combat, audio, wave-telegraph, world generation, zone classifier); it does not emit or subscribe to engine signals.

Entry points

  • GameMaster.tick — per-frame spawn-system update: ramp evaluation, target-on-screen calculation, early-trickle / normal-trickle / wave / minimum-population fills, elite-pack timer, delayed-queue drain, active-count refresh.
  • GameMaster.spawnEnemy — materialize one enemy of a given type at a position (optionally with an arrival angle), pulling from the recycle bin when possible.
  • GameMaster.queueSpawn — schedule a spawn with a delay so bursts stagger across frames.
  • GameMaster.processQueue — drain due entries from the delayed spawn queue.
  • GameMaster.updateCount — refresh activeEnemyCount from the world enemy list.
  • GameMaster.pickTypeForProgress — choose an enemy type id given mission progress, biome / enemy-set routing, and the optional forced-type override.
  • GameMaster.reset — clear all spawner state and accept new run config (quantity/quality curves, randomization, biome id, enemy-set id, level data).
  • spawnEnemy — module-level wrapper around GameMaster.spawnEnemy for test and harness call sites.
  • spawnHorde — convenience batch-spawner that scatters a count around a center point.
  • updateSpawner — compatibility entry point that ticks the delayed queue and refreshes the active count.
  • syncBossSpawnerDisabled — flip the enemy-spawner gate on game based on live boss enemies, leaving the closing-room manual pin alone.
  • releaseEnemy — return a dead enemy slot to the recycle bin after wiping refs, statuses, and lazy-init scratch fields.
  • getEnemyBinSize — telemetry probe for recycle-bin occupancy.
  • tickBossSpawnProfile — advance the boss encounter timer and fire any time / recurring / phase triggers from the active profile.
  • resetBossSpawnProfile — clear all wave-fire bookkeeping at encounter start / end / player death.
  • Director.update — recompute pressure components, smoothed heat, personality bucket, retreat / fire-rate / aggro-range knobs, and mood vector for the frame.
  • Director.updateMood — smooth-lerp the mood vector toward instantaneous mood targets derived from pressure, HP fraction, kill streak, and closest-enemy distance.
  • Director.reset — zero pressure / mood / knobs and re-center personality and heat for a new run.
  • updateDirector — compatibility wrapper around Director.update.
  • makeDirectorState — return a fresh zero mood object for test setup.
  • EnemyBehaviors.register / .get / .can / .list — string-keyed registry of behavior handlers and their capability flags.
  • seekPoint — point-target movement helper used by behaviors and dumb-mode pathing.
  • steerToward — car-like steering helper that caps turn rate and forces forward-along-heading velocity.
  • dumbFace — cheap facing update for dumb-mode enemies far from the player.
  • isMidAttack — predicate that locks an enemy out of dumb-mode while its behavior FSM is in an attack phase.
  • terrainBounce — collide-and-deflect helper for behaviors that should not phase through terrain.
  • terrainPathfind — terrain-aware path query used by behaviors that route around obstacles.
  • rollPersonality — pick a personality profile id for a freshly spawned enemy of a given type.
  • resolveArchetype — map a behavior id (and fallback enemy type) to its archetype label.
  • initAttackState — stamp the shared attack-FSM scratch fields on a new enemy.
  • updateEnemyAI — per-frame walk of every live enemy that dispatches movement and combat through the registered handler.
  • orbit / mosquito / horde_charge / bomber / sniper / guardian / kamikaze — top-level behavior dispatchers exposed for legacy / test call sites.
  • generateRacerLanes — build the lane waypoint set for a city biome from current terrain (with cardinal fallback when terrain is too sparse).
  • querySpawnZone — look up the zone type, base spawn rate, and weighted pool at a world position.
  • pickFromPool — weighted pick from a zone pool given a random roll.
  • shouldSpawn — Bernoulli gate combining a zone base rate, the director multiplier, and frame delta.
  • computeDirectorZoneOutputs — produce the spawn-rate multiplier and elite-chance override for the current run timer / HP fraction / illumination / recent-damage inputs.

Pattern notes

  • Behaviors live in a string-keyed registry; handlers self-register at module load and are looked up by id stored on the enemy entity. Adding an archetype means registering a new handler and routing data to its id — no edits to the per-frame dispatcher.
  • Capability flags on each handler (useSharedAttackFSM, useSharedSeparation, useSharedAggro, stunnable, knockbackable) and a smartRange field gate which shared helpers run for that enemy and at what distance the behavior falls back to cheap dumb-mode movement. Enemies returning true from isMidAttack always run full behavior regardless of distance.
  • Pack aggro is opt-in through _packLeaderId / _packLeaderRef on the enemy: members defer their aggro decision to the leader’s state, while solo enemies auto-aggro.
  • The spawner keeps an enemy recycle bin and lazy-initializes per-behavior scratch fields against === undefined, so releaseEnemy must explicitly wipe those fields back to undefined to re-trigger init on the next life.
  • Spawn pressure has two parallel inputs: a legacy power-curve ramp (internalRamp) and an optional tent-pole keyframe curve (evalCurve) supplied by RunDefinition. Whichever is set drives _ramp; the rest of tick compounds biome / hard-mode / per-run / level / onboarding / overtime / zone multipliers on top.
  • Position-aware spawning samples player position into a short rolling history; spawn direction biases toward the long-run displacement vector rather than instantaneous velocity, and spawn distance scales with current speed.
  • Zone-aware spawning is opt-in via _levelData. When set, the adapter routes per-position pool picks and rate through the precomputed zone grid; when null, the legacy biome / progress routing handles everything.
  • The boss spawn-profile runtime keys its bookkeeping by profileId#waveIndex and only fires one due recurring tick per frame, so a long pause cannot detonate a backlog of waves on a single frame. Pressure-add enemies route through GameMaster.spawnEnemy and never set the boss-bar share flag.
  • The Director writes its outputs into both the singleton Director object and the parallel Knobs object; readers reach into one or the other directly rather than receiving them via call arguments.
  • Racer lane generation falls back to four cardinal straight lanes when terrain has too few pieces to cluster, so racers always have a track to follow even on sparse maps.