PURPOSE
Stand-in-zone event runtime. Each GameEvent is a world-space circular zone the player enters to charge up a reward. All event types share one hold-to-charge state machine; the type field determines only which reward the bridge dispatches on completion. EventManager.update() runs every frame from bridge.ts and returns the list of events that completed this tick.
OWNS
EventTypeunion — twelve event kinds:magnet,levelup,weapon,artifact,wheel,starpower,regen,cascade,forager,horde,comet_shower,pulse,vortex.EventPhaseunion —idle,active,completing,done,failed.GameEventinterface — id, type, position, radius, phase, charge progress, decay/exit timers, mandala VFX phase, completion flash, proximity, optionalhubIdandzoneConstraint.buildDefaultEventPool()— returns a 72-entry weighted array (levelup 27, magnet 10, weapon 6, regen 5, artifact 4, forager 4, horde 3, comet_shower 3, pulse 3, vortex 3, starpower 2, cascade 2) for uniform sampling.createEvent(type, x, y, rng)— factory; assigns id from monotonic counter, sets per-type radius, rolls decay timer in[25, 40)seconds.EventManager.update(events, ship, world, game, dt)— frame tick; advances state machines, removes finished events, fires Sentry first-tick snapshot, returns completed events.EventManager._updateCharge(...)— per-event hold-to-charge logic.EventManager.reset()— resets the id counter and first-tick log flag.- Module-private state:
nextEventIdcounter,_firstTickLoggedflag. - Per-type radius constants:
EVENT_RADIUS_TIGHT = 110(starpower, regen),EVENT_RADIUS_SMALL = 130(levelup),EVENT_RADIUS_LARGE = 220(all others). Charge time isCFG.EVENT_CHARGE_TIME * 1.5.
READS FROM
ShipState—ship.x,ship.yfor proximity and zone test.WorldState—world.seedand(world as any).biomeIdfor the first-tick Sentry tag.GameState—game.tracking.eventsCompletedcounter; incremented on completion.CFG.EVENT_CHARGE_TIMEfrom../core/config.Math.random()for spark VFX scheduling (non-deterministic; gameplay charge progression usesdtonly).- Seeded
rngparameter increateEventfor decay timer and initial mandala phase.
PUSHES TO
- Mutates each
GameEventin place:phase,charged,initiated,exitTimer,_exitWarning,_insideDuration,_mandalaPhase,proximity,completeFlash. - Splices completed/failed non-hub events from the
eventsarray aftercompleteFlashdrains. Particles.add(...)— green-gold spark VFX around the zone perimeter while charging past 10 percent.Particles.burst(x, y, 25, 'spark', {r:80, g:255, b:120}, 100)— completion burst at the event center.game.tracking.eventsCompleted— incremented per completion.Sentry.captureMessage('[EventManager] first-tick snapshot', ...)— single info-level event per run with totals, alive count, hub-tagged count, done/failed counts, ship position, biome id, and seed.- Returns
GameEvent[]of newly completed events to the caller; the bridge fires theevent_completesignal from that list.
DOES NOT
- Does not fire
Sig.event_completeitself. The signal is fired frombridge.tsfor each entry in the returnedcompletedEventsarray, withcev.x,cev.y, andcev.typeas payload. - Does not dispatch any rewards. Reward resolution (level-up, weapon box, magnet sweep, cascade drop, forager scan, horde spawn, pulse shockwave, vortex pull, comet shower, regen, starpower, artifact grant) lives in the bridge’s
completedEventsloop. - Does not place events in the world. World generation and
EventSpawnerown grid placement and distance-based garbage collection. - Does not run decay garbage collection. The
decayTimerfield is set but never decremented here; cleanup is delegated toEventSpawner. - Does not handle full-slot weapon events specially — the chest stays a
weaponevent and the bridge grants+1level to every equipped weapon on pickup. - Does not gate availability by player state. The
regenevent is filtered upstream (until hull damage exists).
Signals
- Outgoing (indirect):
event_complete— fired bybridge.tsfor each completed event returned fromupdate(). Payload:num1=0,num2=0,f1=cev.x,f2=cev.y,tag=cev.type || ''. Thef1/f2coordinates letevent_complete-listening artifacts (registered inengine/world/artifacts.ts) opt into signal-centered placement. - Incoming: none —
events.tsdoes not listen to any signals. - Listeners on
event_complete: legacy artifact handler inengine/world/artifacts.ts(Sig.on('event_complete', _onEventComplete, 50)), plus EffectEngine-routed artifact effects. Used in scenario tests as{ type: 'fire_signal', signal: 'event_complete' }.
Entry points
buildDefaultEventPool(): EventType[]— weighted sampling pool for spawner selection.createEvent(type, x, y, rng): GameEvent— factory.EventManager.update(events, ship, world, game, dt): GameEvent[]— frame tick; called frombridge.ts(gameplay) andtesting/stress-tests.ts.EventManager.reset(): void— call on new run to zero the id counter and re-arm the first-tick Sentry snapshot.
Pattern notes
- Lifecycle phases:
idle(placed, untouched) →active(player entered,initiated=true) →done(charge filled) orfailed(left forexitTimeMax = 8s).completingis declared in the union but the runtime transitions straight fromactivetodone. Afterdone/failed,completeFlash(set to2.0son done,1.0son fail) drains and the event is spliced out. - Accelerating charge: while inside the zone,
_insideDurationaccumulates andchargeAccel = 1 + _insideDuration * 0.10multipliesdtadded tocharged. Charge rewards sustained presence. - Outside drain: leaving an active event drains
chargedat0.15per second, resets_insideDuration, and accumulatesexitTimer. Warning flag flips atexitTimeMax - 5s. Fails atexitTimeMax. - Hub-lantern events (
ev.hubIdset): never spliced. Failed hub events revert toidle(charge, initiated, exitTimer, exitWarning all cleared). Completed hub events keep theirdonephase, play the flash, then persist as permanent beacons. Hub events drain charge while the player is away but never fail (exitTimeris held at0). - Per-type radius (constants above): tight for starpower/regen, small for the common levelup filler, large for premium rewards and combat sub-events so the rings read as the big opportunity.
- Reward type vs runtime: all events share the same state machine and VFX. The
typefield is opaque toevents.ts— it travels throughupdate()unchanged and is consumed by the bridge’s switch oncev.type(level_up,weapon_box,magnet_sweep,cascade,forager,horde,pulse,vortex,comet_shower,regen,starpower,artifact). - Proximity:
proximity = max(0, 1 - dist / (radius * 4)). Drives rendering glow falloff out to four radii from center. - Determinism:
createEventadvancesrngonce after computing the radius (kept as a no-op call so seeds generated before the per-type size change still produce the same downstream sequence), then again for the decay timer. Spark scheduling usesMath.random()and is intentionally non-deterministic VFX. - Diagnostics: first call to
update()per run emits a Sentry info event taggedsystem=event-manager, stage=first-tickwith totals/alive/done/failed/hubTagged counts, ship position, biome id, and seed. Guarded by_firstTickLogged, cleared inreset(). - Decay field unused here:
EVENT_DECAY_RANGE = [25, 40]writesdecayTimerfor downstream consumers; in-loop comment confirms decay has been removed from this module in favor ofEventSpawnerdistance-based GC.