Shared TypeScript type definitions for the bridge subsystem — the contract between the React shell (GameScreen, metagame UI) and the game engine’s mission runtime. Two interfaces: BridgeCallbacks (engine → UI) and MissionHandle (UI → engine).
Imports
MissionResult from ../data/mission-result — snapshot payload for end-of-run / early exit.
LevelTrigger, LevelConfig from ../data/level-config — sandbox playground level shape.
ModInstance from ../data/mods — equipped mod entries for sandbox testing.
Snapshot run state without ending it. Used for cog-menu early exit + beforeunload browser close.
recoverFromStuck
() => void
Clear VFX and enemies; preserve ship progress (weapons, artifacts, level, xp) and mission timer. Safeguard for PWA zoom/render bugs that would otherwise ruin a run.
Notify engine that GameScreen resized the canvas. GameScreen owns canvas.width / style sizing; engine syncs W/H, ctx-transform DPR, WebGL sprite-batch backing buffers. Single source of truth — no other resize listener should write to the canvas.
Sandbox API
Only available on missions with sandbox=true. Playground / artifact-testing tools.
Ship + weapons + world
Method
Signature
patchShipStats
(patch: Record<string, any>) => void
setWeapons
(weapons: Array<{ id: string; level: number }>) => void
Synchronously run one frame with synthetic timestamp.
Key design notes
Optional callbacks on BridgeCallbacks (onRewardShow, onRevivePrompt, onReviveDismissed) — non-fatal if UI omits them; engine no-ops.
Sandbox methods are unconditional on the type but runtime-gated; calling them outside sandbox missions is undefined behavior (engine-side enforcement, not type-side).
Canvas resize ownership: GameScreen is the sole writer of canvas.width / style; onCanvasResize is the only path for the engine to learn about it. No other resize listener may write to the canvas.
Death-defiance flow: UI subtracts gems before calling revive(). Engine trusts the caller.
Reroll / banish: charge-counted; engine tracks state, UI just calls the action. Banished keys persist for the run.
Type laxity:onRewardShow uses any[] for choices; patchShipStats uses Record<string, any> — sandbox-tooling shape, not the main reward/ship type contract.
EXTRACT-CANDIDATE
The 50+ sandbox-prefixed methods are a candidate to split into a separate SandboxHandle interface returned from MissionHandle when sandbox=true. Current shape keeps everything on one handle; this bloats the type for production missions that never use sandbox surfaces and makes runtime gating implicit.
onRewardShow: (choices: any[]) => void — the any[] weakens the contract; an explicit RewardChoice shape from ../data/rewards would let consumers type-narrow without a cast.
patchShipStats: (patch: Record<string, any>) => void — same pattern; a Partial<ShipStats> would catch typos and stat-name drift at compile time.
Camera/ship-query getters (getShipScreenPos, getShipAngle, getCameraTransform) and snapshot getters could form a MissionQuery sub-interface, separating side-effecting actions from read-only views.
BridgeCallbacks lacks a phase enum — onPhaseChange(phase: string) should reference a MissionPhase union from the engine’s state module to prevent UI mis-handling unknown phases silently.