bridge-phase-state.ts
Mutable state bundle shared across bridge subsystem modules. All closure-local state from createMission() is consolidated into a single MissionState object so extracted subsystem modules can read/write without capturing closure variables. Created once per mission, threaded through every subsystem function.
Role in the engine
When createMission() was monolithic, subsystem code lived inside its closure and read free variables. After extraction, those variables had to be hoisted into an explicit container so the new modules could be pure functions over state. This file is that container — no behavior, only typed shape + a zero-value factory.
Exports
| Symbol | Kind | Purpose |
|---|---|---|
MissionState | interface | Typed shape of the shared mutable bundle. |
createMissionState(ctx, canvas, renderer, runDef, callbacks, missionResult, seed, dpr) | function | Allocate a fresh MissionState with defaults; called once at the top of createMission(). |
MissionState fields by group
Canvas / renderer refs
ctx: CanvasRenderingContext2D— main 2D context.canvas: HTMLCanvasElement— backing canvas element.renderer: Renderer— engine renderer instance.
Run configuration
runDef: RunDefinition— full run config blob (player, timing, etc.).callbacks: BridgeCallbacks— host hooks back into the React/metagame shell.missionResult: MissionResult— accumulator written through the run, returned on exit.seed: number— RNG seed for the mission.
Hot-path config locals (destructured for perf)
autoAimRange— fromrunDef.player.autoAimRange.deadzone— fromrunDef.player.deadzone.enemyFlashDecay— fromrunDef.timing.enemyFlashDecay.damageFlashDecay— fromrunDef.timing.damageFlashDecay.deathTimerDuration— fromrunDef.timing.deathTimer.
DPR
frameDpr: number— device-pixel-ratio captured at frame start.
Death / revive state
deathElapsed: number— seconds since death began (0 initially).deathDefianceUses: number— defiance revive charges consumed this mission.revivePromptActive: boolean— true while the revive-or-decline prompt is up.revivePromptTimer: number— countdown for the prompt.deathCinematicPlaying: boolean— gates the death sequence.deathCinematicTimer: number— seconds into the cinematic.postDeclineDeath: boolean— true after the player declined revive.
Mission complete timer
completeElapsed: number— seconds since mission-complete fired.
Level-up ring VFX
levelupRing: { x, y, l, ml, maxRad } | null— single transient ring;l/mlare life/max-life,maxRadis target radius.
Engine trail buffer
trail: Array<{ x, y }>— circular buffer of trail samples.trailIdx: number— write head.
Terrain expansion throttle
lastExpandGridX,lastExpandGridY— last grid cell that triggered expansion (init-99999).lastFastExpandTime: number— last wall-clock for fast-expand throttle.
Frame scheduling
rAF: number | null— currentrequestAnimationFramehandle.paused: boolean— frame loop pause flag.frameErrors: number— cumulative frame-error count.
Thermal rest
thermalRest: boolean— true while the mission is paused for thermal rest.thermalRestSnapshot: HTMLCanvasElement | null— frozen frame shown during rest.
Saved weapon modes
savedWeaponModes: Array<{ fireMode: 'auto' | 'manual' }>— per-slot fire-mode snapshot; defaults to four'auto'entries.
Timed weapon-chest schedule
wcNextSpawnTime: number— next chest spawn time (init-1).wcChecksDone: number— completed spawn checks.wcSpawnedThisLevel: number— chests spawned in current level.wcPendingChest: any— staged chest awaiting spawn slot.
Dynamic artifact drop state
artifactNextDropTime: number— next eligible drop time (init-1).artifactLastKillSnapshot: number— kill count at last snapshot.artifactLastSnapshotTime: number— wall-clock of last snapshot.
Illumination / trigger systems
illuminationSystem: IlluminationSystem | null— populated after init.triggerSystem: TriggerSystem | null— populated after init.zoneDebugEnabled: boolean— toggle for zone debug overlay.hubSpokeWireframeEnabled: boolean— toggle for hub-spoke wireframe overlay.
Event pool (for level advance)
eventPool: any[]— pending events drained on level-advance.
Init planet refs
initPlanet: any— initial planet ref captured at boot.initLevelData: LevelData | undefined— initial level-data blob.
createMissionState defaults
All numeric counters start at 0, all booleans at false, all reference fields at null/undefined/[]. The five hot-path config locals are pulled from runDef. lastExpandGridX/lastExpandGridY are seeded to -99999 so any real grid cell triggers expansion on first frame. wcNextSpawnTime and artifactNextDropTime are seeded to -1 (sentinel: “not yet scheduled”). savedWeaponModes is pre-populated with four 'auto' slots.
Imports
RunDefinitionfrom../data/run-configMissionResultfrom../data/mission-resultBridgeCallbacksfrom./bridge-typesRendererfrom./rendering/rendererIlluminationSystemfrom./world/illuminationTriggerSystemfrom./world/trigger-systemLevelDatafrom./world/chunk-manager
Lifecycle
createMission()callscreateMissionState(...)once at the top.- The returned object is threaded into every extracted subsystem function as a
stateparameter. - Subsystems mutate fields in place — no copies, no events.
- On mission end, the host reads
missionResult(which subsystems have populated through the run) and discards the rest.
Anti-patterns
- Subsystem modules must not capture closure variables from
createMission()— everything lives onMissionState. - No method-style behavior belongs on
MissionState; it is a data bag, not a class. - Field types
any(wcPendingChest,eventPool,initPlanet) are deliberate escape hatches pending stronger typing of those subsystems.
EXTRACT-CANDIDATE
- The
any-typed fields (wcPendingChest,eventPool[],initPlanet) are extraction targets — each should be replaced with a concrete type owned by its subsystem module (weapon-chest, event-pool, world-init) and re-imported here. - The four field groups for weapon-chest scheduling, artifact drop, terrain throttle, and thermal rest read like cohesive sub-bundles; each could be lifted into its own
WeaponChestState/ArtifactDropState/TerrainExpandState/ThermalRestStatenested object to shrink the flat surface. savedWeaponModeshard-codes 4 slots; if slot count ever becomes data-driven the default initializer needs to derive length fromrunDef.