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
- Trigger sits dormant in
triggers[]aftercreateTriggerSystem. - Each frame,
tickTriggerscomputesinside = hypot(playerX - t.x, playerY - t.y) < t.radius. - On the edge (
inside && !wasInside), if the trigger hasn’t fired (oroneShot === false), the matching callback runs. - One-shot triggers are added to
firedIdsand never fire again. Repeatable triggers re-fire on each new entry. _insideis 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:
| Type | Callback | Typical use |
|---|---|---|
dialogue | onDialogue | Scripted barks, mission narration |
spawn_wave | onSpawnWave | Drop a pre-authored enemy wave |
spawn_zone | onSpawnZone | Activate a continuous spawner region |
checkpoint | onCheckpoint | Mission progress / save point |
event | onEvent | Generic 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
TriggerSystemstruct. - 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 calltickTriggersfrom the main loop. - Different from effect-engine triggers (which hook combat events on entities); this layer only watches spatial player position.