Trigger System

World-level proximity triggers used to drive mission objectives and scripted events. A LevelTrigger is a generic record placed in level data: { id, x, y, radius, type, oneShot }. The system polls the player position each tick and fires the matching callback the frame the player crosses into the radius.

This is the world-trigger layer (place an invisible volume on the map and run something when the ship flies through it). It is distinct from effect-engine triggers (status-effect / projectile event hooks) — those listen to combat events on entities, not spatial volumes.

Lifecycle per trigger

  1. Trigger sits dormant in triggers[] after createTriggerSystem.
  2. Each frame, tickTriggers computes inside = hypot(playerX - t.x, playerY - t.y) < t.radius.
  3. On the edge (inside && !wasInside), if the trigger hasn’t fired (or oneShot === false), the matching callback runs.
  4. One-shot triggers are added to firedIds and never fire again. Repeatable triggers re-fire on each new entry.
  5. _inside is updated every frame so the next entry can be detected.

Trigger types

The type field selects which callback runs. Each maps to a callback on TriggerCallbacks:

TypeCallbackTypical use
dialogueonDialogueScripted barks, mission narration
spawn_waveonSpawnWaveDrop a pre-authored enemy wave
spawn_zoneonSpawnZoneActivate a continuous spawner region
checkpointonCheckpointMission progress / save point
eventonEventGeneric catch-all for custom scripted events

The trigger system itself only routes — all gameplay consequence lives in the registered callbacks.

One-shot vs repeatable

oneShot defaults to true (any value other than literal false is treated as one-shot). Set oneShot: false in level data when you want a trigger to keep firing every time the player re-enters its radius — for example, a refilling buff zone, or a recurring ambush spawner.

Key facts

  • File: engine/world/trigger-system.ts.
  • Pure data + functions — no class instances, all state on the TriggerSystem struct.
  • Tick driver passes (playerX, playerY); no other entities are checked, the player is the sole tripwire.
  • Detection uses raw Euclidean distance (Math.hypot), so radii are circles, not boxes.
  • Edge-detection prevents re-firing while the player is parked inside a radius — they must leave and re-enter to fire a repeatable trigger again.
  • Wire it via createTriggerSystem(triggers, callbacks) once per level, then call tickTriggers from the main loop.
  • Different from effect-engine triggers (which hook combat events on entities); this layer only watches spatial player position.