WaveDirector
Enemies slice. Lives on `Game`. On `waveStarted` it queues `R.waveSpec(wave)` (shuffled, the boss first through the 180-degree gate) and pours enemies through the four gate mouths (`R.GATE_ANGLES`) as dynamic sphere bodies (rotation locked, wisps have no gravity). Enemies chase t…
WaveDirector
Enemies slice. Lives on Game. On waveStarted it queues R.waveSpec(wave) (shuffled, the boss first through the 180-degree gate) and pours enemies through the four gate mouths (R.GATE_ANGLES) as dynamic sphere bodies (rotation locked, wisps have no gravity). Enemies chase the player, wind up and call R.hurtPlayer(def.damage) with a shove, get knocked flying by hitEnemies, and die with a pop of shards. Each death drops def.drops bones from R.rollBone; the pickups glow, bob, spin, drift gently toward a nearby player and are collected within R.PICKUP_RADIUS via R.collectBone. When the queue is empty and nothing is alive, leftover bones fly to the player, then R.clearWave() runs.
Runtime entities are temporary children of Game: enemy_<kind>_<n> (with a visual child), bone_pickup_<boneId>, pop_shard/pop_flash.
Providers
hitEnemies(center {x,y,z}, radius, damage, knockback, kind) -> hitsnearestEnemy(pos {x,y,z}, maxDist) -> { id, pos, kind, dist } | nilenemyCount() -> alive, queued(extra)
Events emitted
enemyHit { id, kind, pos, damage, attack }, enemyKilled { id, kind, pos }, boneDropped { bone, pos }.
Public fields
maxAlive(22) most enemies in the arena at once.spawnInterval(0.45 s) between gate spawns.clearDelay(1.4 s) between the last kill andR.clearWave.
Methods
:counts() -> alive, queued:killAll() -> n(test helper)
Visuals
game.enemies.visuals builds each enemy. If a bundle game.enemies.models.<kind> (ghoul/imp/wisp/golem) exists it is used automatically, scaled to V.MODEL_HEIGHT[kind]; otherwise a hand-assembled stand-in (primitives + generated burlap/pumpkin/tombstone PBR materials) is used. Concept images for the mesh generation are in concepts/.
Player position
Uses R.playerPos(); while R.state.playerEntity is unset it falls back to the entity named body carrying PlayerBody.
Scoped to this part · feeds back into the world's score.