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
Knobstuning 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/enemiesfor 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/weaponsfor enemy weapon stat blocks used by combat ticks.data/planet-configfor per-planet spawn grace seconds.data/spawn-profilesfor boss-encounter wave definitions, trigger kinds, and position selectors.data/level-configfor zone types and per-zone spawn pool / rate config.engine/corestate (sharedgame,ship), types, clock, and the frame-cached alive-enemy count.engine/world/generationfor the world generator surface used during spawn placement.engine/world/chunk-managerfor the per-levelLevelDatapackage (zone grid, hub/spoke layout, illumination map, level config).engine/world/zone-classifierto look up the zone type at any world position.engine/affixes/rollfor elite affix rolls applied to spawned packs.
PUSHES TO
world.enemies— every spawn, every per-frame integration, every death-driven removal.engine/combatviadamagePlayerwhen enemy attacks land.engine/audioviaMicroSfxfor spawn whooshes.engine/world/wave-telegraphviafireWaveTelegraphwhen 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, anddata/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, andresetBossSpawnProfile.
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— refreshactiveEnemyCountfrom 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 aroundGameMaster.spawnEnemyfor 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 ongamebased 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 aroundDirector.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 asmartRangefield gate which shared helpers run for that enemy and at what distance the behavior falls back to cheap dumb-mode movement. Enemies returning true fromisMidAttackalways run full behavior regardless of distance. - Pack aggro is opt-in through
_packLeaderId/_packLeaderRefon 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, soreleaseEnemymust explicitly wipe those fields back toundefinedto 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 byRunDefinition. Whichever is set drives_ramp; the rest oftickcompounds 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#waveIndexand 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 throughGameMaster.spawnEnemyand never set the boss-bar share flag. - The Director writes its outputs into both the singleton
Directorobject and the parallelKnobsobject; 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.