engine/bridge-types

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.

BridgeCallbacks (engine → UI)

Engine fires these into React when state changes.

CallbackSignaturePurpose
onPhaseChange(phase: string) => voidRequired. Phase transitions (playing → reward → gameover etc.)
onGameOver(result: MissionResult) => voidRequired. Run ended; deliver result for hub processing.
onRewardShow?(choices: any[]) => voidOptional. Reward picker should render the given choices.
onRevivePrompt?(gemCost: number, useNumber: number) => voidOptional. Player died with death-defiance available; show revive prompt with cost.
onReviveDismissed?() => voidOptional. Revive prompt declined or timed out.

MissionHandle (UI → engine)

Returned by mission start. UI calls these to drive the running game.

Lifecycle

MethodSignatureNotes
start() => voidBegin mission loop.
pause() => voidHalt sim updates.
resume() => voidResume sim updates.
destroy() => voidTear down engine, release resources.

Input

MethodSignatureNotes
setInput(angle: number, magnitude: number, thrusting: boolean) => voidJoystick / aim input.
setWeaponModes`(modes: Array<{ fireMode: ‘auto''manual’ }>) void`
fireWeapon(slotIndex: number) => voidManual fire on 0-indexed slot. Fires only when cooldown ready.

Reward flow

MethodSignatureNotes
selectReward(index: number) => voidPick reward option.
rerollRewards() => voidSpend 1 reroll charge — regenerate choices for the active family, honoring run banish list. No-op at 0 charges.
toggleBanishTargeting() => voidEnter / exit banish-targeting mode (player-cancellable).
banishOption(index: number) => voidSpend 1 banish, mark its key in banishedKeys (can’t appear later this run), play smoke-vanish animation.
wheelSpin() => voidTrigger wheel spin (reward wheel mechanic).
wheelExit() => voidExit wheel screen.

Death defiance

MethodSignatureNotes
revive() => voidAccept death defiance. Caller must spend gems before calling.
declineRevive() => voidDecline; proceed to normal death.

Camera / ship queries

MethodSignatureNotes
getShipScreenPos() => { x: number; y: number }Sprite center as projected by camera.
getShipAngle() => numberWorld-space facing angle, radians.
getCameraTransform() => { x: number; y: number; zoom: number; w: number; h: number; dpr: number }Camera and viewport snapshot.

Snapshot / recovery

MethodSignatureNotes
getRunSnapshot(causeOfDeath: string) => MissionResultSnapshot run state without ending it. Used for cog-menu early exit + beforeunload browser close.
recoverFromStuck() => voidClear 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.
onCanvasResize(cssWidth: number, cssHeight: number, dpr: number) => voidNotify 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

MethodSignature
patchShipStats(patch: Record<string, any>) => void
setWeapons(weapons: Array<{ id: string; level: number }>) => void
setWorldKnobs(knobs: Partial<{ enemyHpMult: number; enemyDamageMult: number; enemySpeedMult: number; enemyCountMult: number; rewardMult: number; rarityScale: number }>) => void
setSpawnerEnabled(enabled: boolean) => void
setGodMode(on: boolean) => void
fullHeal() => void
sandboxRespawn() => void — restore HP/shield, set alive, resume playing
sandboxResetForTest() => void — clears stats, artifacts, enemies, bullets

Enemies

MethodSignature
spawnEnemyAt(typeId: string, worldX: number, worldY: number) => void
clearEnemies() => void
freezeEnemies(on: boolean) => void
getAliveEnemyCount() => number

Artifacts / mods

MethodSignature
sandboxGrantArtifact(artifactId: string) => void
sandboxEquipMods(mods: ModInstance[]) => void — apply equipped mod instances to live sandbox ship without restarting

Terrain

MethodSignature
sandboxPlaceTerrain(shapeId: string, worldX: number, worldY: number, scale: number, rotation: number) => number — returns index in world.terrain
sandboxRemoveTerrain(index: number) => void
sandboxMoveTerrain(index: number, worldX: number, worldY: number) => void
sandboxScreenToWorld(screenX: number, screenY: number) => { x: number; y: number }
sandboxGetTerrainCount() => number
sandboxRegenerateWorld(biomeId: string) => void — keep ship, clear terrain, swap biome

Visuals

MethodSignature
sandboxSetPostProcessing(config: { bloomAlphaMult?: number; contrastBoost?: number; saturationShift?: number; vignetteEnabled?: boolean }) => void
sandboxSetFog(config: { fogBelow?: boolean; fogAbove?: boolean; fogBelowAlpha?: number; fogAboveAlpha?: number; dustEnabled?: boolean; bokehEnabled?: boolean; fogBelowTex?: string; fogAboveTex?: string; fogBelowTintR/G/B?: number; fogAboveTintR/G/B?: number; fogShipCutout?: number }) => void
sandboxSetNebula(archetypeIdx: number) => void
sandboxSetLighting(config: { enabled: boolean; darkOpacity?: number; ambientLight?: number; baseRadius?: number; dimWidth?: number; enemyLights?: boolean; bulletLights?: boolean }) => void

Level config / playground

MethodSignature
sandboxSetSpawnRate(value: number) => void — update spawn quantity curve base (0–1)
sandboxSetLevelConfig(config: Partial<LevelConfig>) => void — rebakes level data for live preview
sandboxAddTrigger(trigger: LevelTrigger) => void
sandboxUndoTrigger() => LevelTrigger | null — removes most-recently-added
sandboxGetTriggerCount() => number
sandboxGetLevelStats() => { hubCount: number; spokeCount: number; precomputeMs: number; terrainCount: number; eventCount: number; zoneGridCells: number }

Debug overlays

MethodSignature
sandboxSetDebugOverlay(on: boolean) => void — collision debug
sandboxSetZoneDebug(enabled: boolean) => void
sandboxSetHubSpokeWireframe(enabled: boolean) => void

Boss

MethodSignature
sandboxSpawnBoss(bossId: string, tier: number) => void — bypasses end-of-level trigger, opens boss room first if needed
sandboxGetBossState() => { alive: boolean; hp: number; maxHp: number; phase: number; roomState: string; partsAlive: number; fightElapsed: number } | null

Test-only

MethodSignatureNotes
testSetManualPump(enable: boolean) => voidDisable auto-scheduler; enable manual frame pumping.
testPumpFrame(fakeTimestampMs: number) => voidSynchronously 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.