run-effects.ts

PURPOSE

Orchestrates per-run wiring of the EffectEngine. At mission start it clears prior state, registers installed mod effects (placeholder) and active artifact effects, primes the engine with game/ship references, subscribes to signals, and fires the one-shot run_start trigger. Replaces the legacy initArtifacts() call. Provides matching teardown for mission end.

OWNS

  • registerRunEffects(game, ship, activeArtifacts) — exported entry that performs the full clear-register-init sequence.
  • teardownRunEffects() — exported entry that clears the EffectEngine at mission end.
  • The fixed run-start sequence: clear → mod effects (placeholder) → artifact effects → prime tick → init.
  • The convention that every registered effect group gets a source tag of the form artifact:<id>.

READS FROM

  • EffectEngine (./effect-engine) — calls clear, register, tick, init.
  • Sig (../core/signals) — imported for the signal-firing contract referenced in the file header (run-start fan-out is documented as happening through engine init subscriptions).
  • GameState, ShipState (../core/types) — passed through to the priming tick.
  • ARTIFACT_MAP, getTierValuesAt (../../data/artifacts) — looks up artifact definitions by inst.id and resolves runtime tier index (0-4) to the underlying 4-tuple tier values.
  • ArtifactInstance (../world/artifacts) — input array element type; supplies id and tier.

PUSHES TO

  • EffectEngine internal state — populates the engine’s effect registry with artifact effect groups tagged artifact:<id> and their tier values.
  • EffectEngine signal subscriptions — established when init() runs after registration.

DOES NOT

  • Does not evaluate or trigger individual effects itself; that is the EffectEngine’s responsibility.
  • Does not iterate the world entity lists; the priming tick passes an empty { enemies, playerBullets, enemyBullets, particles } snapshot.
  • Does not register mod effects yet — Phase A is a placeholder reserved for Phase 6 to populate from ModTemplate.effects.
  • Does not skip artifacts based on tier; only skips when the artifact id is missing from ARTIFACT_MAP or has no effects array.
  • Does not unregister individual effects — teardown clears the entire engine.
  • Does not assemble the run definition; expects to be called after assembleRunDef but before gameplay begins.

Signals

  • Header documents firing run_start via Sig.fire('run_start') as step 5 of the sequence; subscribers are wired through EffectEngine.init() so that any registered effect listening for run_start runs once at mission start.
  • Sig is imported but the file relies on the engine’s subscription model — handlers attach during EffectEngine.init().

Entry points

  • registerRunEffects(game: GameState, ship: ShipState, activeArtifacts: ArtifactInstance[]): void — called by bridge.ts at mission start, after run assembly and before gameplay begins.
  • teardownRunEffects(): void — called by bridge.ts at mission end.

Pattern notes

  • Fixed phase order: clear, Phase A (mod effects, placeholder), Phase B (artifact effects), Phase D (priming tick + init). Phase C is implicitly reserved.
  • Each artifact contributes a single grouped registration to the engine, scoped by a stable source tag of artifact:<id>, enabling later targeted teardown if needed.
  • Tier resolution is centralised through getTierValuesAt(def, inst.tier) so callers never index the raw 4-tuple directly; the returned values are passed to the engine for $var substitution inside effect param payloads.
  • A zero-delta priming tick (EffectEngine.tick(0, game, ship, emptyWorld)) is invoked before init() so signal handlers have cached game/ship refs available immediately. The empty world snapshot is cast through as any because no entities exist yet at run-start.
  • Artifacts with no effects array or an empty one are silently skipped — no warning, no crash.
  • Teardown is symmetric and minimal: a single EffectEngine.clear() call removes everything in one shot, matching the run-scoped lifecycle.