The pack
enemyCapture
function enemyCapture(): Pack<{ enemyCapture: typeof api }>
Install the renderer-independent enemy capture API.
Helpers
CAPTURE_SCORING
const CAPTURE_SCORING: CaptureScoring
The default scoring: doubling pops from 1000, a fruit per kill, doubling snowball chains from 500.
CAPTURE_TIMING
const CAPTURE_TIMING: CaptureTiming
The default timings, seconds.
captureConfig
function captureConfig(o: CaptureConfigInput): CaptureConfig
A complete configuration from a game's own parts, with plain defaults for the rest.
captureDifficulty
function captureDifficulty(name: CaptureDifficultyName): CaptureDifficulty
Difficulty presets: Arcade is the original tables, Normal is the gallery default, Easy is gentler still.
createCaptureGame
function createCaptureGame(initial: CaptureConfig): CaptureGame
Create a capture game from a configuration (see bubbleBobblePreset and snowBrosPreset).
Types
CaptureAnger
interface CaptureAnger
Anger: enemies that escape, the hurry-up and the last enemy speed up.
speed: number— Speed multiplier when angry.onEscape: boolean— An enemy that breaks out of a bubble or snow is angry.onHurry: boolean— Every enemy turns angry at the hurry-up.lastEnemy: boolean— The last enemy is angry.calmOnDeath: boolean— A player's death calms every enemy.
CaptureConfig
interface CaptureConfig
A complete capture game configuration. Presets build these; every field is data.
name: string— Name.mechanic: "bubble" | "snow" | "both"— The capture mechanic(s): bubbles, snow or both at once.tile: number— Tile size of the round maps.legend: Readonly<Record<string, LegendEntry>>— Legend for round rows.wrapY: boolean— Maps wrap bottom to top.rounds: readonly CaptureRound[]— The rounds, in order.loop: boolean— After the last round: start again fromloopTo(true) or finish (false).loopTo: number— Round to loop back to (1-based).startRound: number— Starting round (1-based).players: number— Players, 1 to 4.player: CapturePlayer— Player rules.kinds: Record<string, CaptureEnemyKind>— Enemy kinds.shots: Record<string, ShotSpec>— Enemy projectiles by name (behaviours fire them by name).bubbles: Partial<BubbleSettings>— Bubble rules (mechanic 'bubble' or 'both').snow: Partial<SnowSettings>— Snow rules (mechanic 'snow' or 'both').elements: Partial<ElementSettings>— Elemental effects (special bubbles).scoring: CaptureScoring— Scoring.items: CaptureItems— Items.powerups: Record<string, CapturePowerup>— Named power-ups.letters: CaptureLetters | null— Bonus-word letters, or null.anger: CaptureAnger— Anger.hurry: CaptureHurry | null— Time pressure, or null.timing: CaptureTiming— Timings.difficulty: CaptureDifficulty— Difficulty scaling.seed: number— Seed.invulnerable: boolean— Players never die (tests, demos).
CaptureConfigInput
type CaptureConfigInput = Pick<CaptureConfig, "name" | "mechanic" | "rounds" | "kinds"> & Partial<Omit<CaptureConfig, "name" | "mechanic" | "rounds" | "kinds" | "player" | "scoring" | "items" | "timing" | "anger">> & { /** The player: a walker profile and start points; lives, fire rate, auto-fire and push speed are optional. */ player: Pick<CapturePlayer, "profile" | "start"> & Partial<CapturePlayer>; /** Scoring overrides. */ scoring?: Partial<CaptureScoring>; /** Item overrides. */ items?: Partial<CaptureItems>; /** Timing overrides. */ timing?: Partial<CaptureTiming>; /** Anger overrides. */ anger?: Partial<CaptureAnger>; }
What captureConfig needs: the game's own parts, and any section to override.
CaptureCounter
type CaptureCounter = "blown" | "empty-pops" | "jumps" | "distance" | "lightning-pops" | "fire-pops" | "water-pops" | "wraps" | "fruits" | "specials"
Counters the game keeps across rounds (Bubble Bobble's special item triggers).
CaptureDifficulty
interface CaptureDifficulty
Difficulty and population scaling applied on top of any preset.
name: string— Name.speed: number— Enemy speed multiplier.trap: number— Multiplier on how long trapped (bubbled) enemies and snowballs hold.minTrap: number— Shortest trapped time, seconds (late Bubble Bobble rounds trap for 1 s in the arcade).patience: number— Multiplier on the hurry-up and chaser timers.chaser: number— Chaser speed multiplier.population: number— Fraction of each round's authored enemies that appear (at least one).lives: number— Extra starting lives.shots: number— Multiplier on the gap between enemy shots (2 = half as many shots).minLetters: number— Letters offered on letter rounds even without a big pop (0: the arcade rule only).column: 0 | 1 | 2 | 3— Which of the four threshold columns of a counter item applies (Bubble Bobble's AA BB CC DD: 0 hardest .. 3 normal, 2 easy).
CaptureDifficultyName
type CaptureDifficultyName = "easy" | "normal" | "arcade"
Difficulty preset names. Arcade is the original tables; Normal (the gallery default) and Easy are gentler.
CaptureEffect
type CaptureEffect = | { kind: "power"; power: string } | { kind: "smart-bomb"; value: number } | { kind: "skip"; rounds: number } | { kind: "stop-timer" } | { kind: "freeze"; seconds: number; value: number } | { kind: "shower"; count: number; value: number; bonus: number; seconds: number }
What a special item does.
CaptureEnemyKind
interface CaptureEnemyKind
An enemy kind in a capture game: a platform-enemies kind plus capture rules.
enemy: EnemyKind— The platform-enemies kind (walker profile, movement or custom behaviour, modifiers).capturable?: boolean— Can be bubbled or snowed. Default true.deadly?: boolean— Touching it kills a player. Default true.boss?: { hp: number; bonus: number; hurtBy: "ball" | "lightning" | "both" } | null— A boss: hit points, what hurts it, the bonus for beating it.letter?: boolean— Carries a letter of the bonus word when killed (Snow Bros' face creatures). Default false.required?: boolean— Must be cleared for the round to end. Default true (Snow Bros' Doskoi sentries are optional).
CaptureEnemyView
interface CaptureEnemyView
An enemy as read by renderers.
id: string— Id.kind: string— Kind.x: number— Centre x.y: number— Centre y.w: number— Size.h: number— Size.facing: -1 | 1— Facing.mode: WalkerMode— Movement mode.vx: number— Velocity x.state: "entering" | "active" | "covered" | "snowball" | "rolling" | "frozen"— 'entering' (dropping in), 'active', 'covered' (partly snowed), 'snowball', 'rolling', 'frozen'. Trapped enemies are drawn from their bubbles.angry: boolean— Angry (faster, recoloured).snow: number— Snow units (0 .. cover).hp: number— Boss hit points left (0 for non-bosses).hpMax: number— Boss hit points at the start.hurt: number— Seconds since its last boss hit (flash).snapped: boolean— Discontinuous move: snap the sprite.
CaptureEvent
type CaptureEvent = | { kind: "round-start"; round: number; name: string } | { kind: "round-clear"; round: number } | { kind: "score"; player: number; value: number; x: number; y: number; reason: string } | { kind: "blow"; player: number } | { kind: "throw"; player: number } | { kind: "kick"; player: number; enemy: string } | { kind: "capture"; enemy: string; enemyKind: string; x: number; y: number } | { kind: "escape"; enemy: string; enemyKind: string } | { kind: "pop"; player: number; enemies: number; bubbles: number; x: number; y: number } | { kind: "snowball"; enemy: string } | { kind: "kill"; enemy: string; enemyKind: string; cause: string; x: number; y: number } | { kind: "collect"; player: number; item: string; value: number; x: number; y: number } | { kind: "powerup"; player: number; power: string } | { kind: "letter"; player: number; letter: string; index: number } | { kind: "extend"; player: number } | { kind: "extra-life"; player: number; lives: number } | { kind: "hurry" } | { kind: "chaser"; id: string; chaser: string } | { kind: "death"; player: number; cause: string } | { kind: "respawn"; player: number } | { kind: "all-in-one"; player: number; reward: "items" | "face" } | { kind: "boss-hit"; enemy: string; hp: number } | { kind: "boss-defeated"; enemy: string; bonus: number } | { kind: "element"; element: "water" | "fire" | "lightning"; x: number; y: number } | { kind: "game-over" } | { kind: "complete" }
Events from drain().
CaptureGame
interface CaptureGame
A running capture game.
readonly config: CaptureConfig— The configuration (resolved).readonly status: CaptureStatus— Current status.readonly round: number— Current round (1-based).readonly map: PlatformMap— The live map, world, director and fields of the current round (rebuilt each round).readonly world: WalkerWorld— Walker world.readonly enemies: EnemyDirector— Enemy director.readonly bubbles: BubbleField | null— Bubble field (bubble mechanic) or null.readonly snow: SnowField | null— Snow field (snow mechanic) or null.readonly elements: Elements— Elemental effects.control(player: number, input: Partial<CaptureInput>): void— Set a player's input.tick(seconds: number): number— Advance by elapsed seconds (whole fixed steps of 1/60 s); returns the steps run.step(): void— One fixed step.readonly state: CaptureState— A fresh copy of the state for renderers.drain(): CaptureEvent[]— Events since the last drain.configure(patch: CapturePatch): void— Change rules while the game runs: bubble, snow, element and anger settings, scoring, timings, power-ups, letters, hurry, shots and the round list. The live fields of the current round are reconfigured at once (a round's trapped time and float speed are recomputed from the new rounds); everything applies to later rounds. Structural fields (mechanic, tile, legend, wrap, players, kinds, difficulty, seed) need a new game.goToRound(n: number): void— Jump to a round (1-based), keeping scores and lives.reset(): void— Start a new game from the configured start round.snapshot(): CaptureSnapshot— Serialisable state.restore(s: CaptureSnapshot): void— Restore a snapshot taken from a game with the same config.
CaptureHurry
interface CaptureHurry
Time pressure: a hurry-up, then an invincible chaser (and optional extra chasers later).
after: number— Seconds before the hurry-up (rounds may override).chaserAfter: number— Seconds after the hurry-up before the chaser (rounds may override).chaser: ChaserSpec— The chaser.perPlayer: boolean— One chaser per live player (Bubble Bobble) or one in all.extra: { spec: ChaserSpec; after: number; every: number; max: number } | null— Extra chasers released later (Snow Bros' slime ghosts).
CaptureInput
interface CaptureInput
Player input for one step.
x: number— -1 left .. 1 right.y: number— -1 up .. 1 down.jump: boolean— Jump held.fire: boolean— Fire (blow, throw, kick) held.
CaptureItems
interface CaptureItems
Item timing and tables.
pointsAfter: number— Seconds into a round when the points item appears (0: never).specialAfter: number— Seconds into a round when the special item appears.pointsTable: { item: string; value: number }[]— Points items (value by index). Empty: no points items.pick?: (ctx: PointsPickContext) => number— Which entry ofpointsTablea round's points item is (Bubble Bobble's ROM rule isbubbleBobblePointsPick). Default a random entry.specials: CaptureSpecialItem[]— Counter-driven special items.
CaptureItemView
interface CaptureItemView
A pickup or a flying corpse.
id: string— Id.item: string— Item name.kind: "corpse" | "fruit" | "points" | "special" | "diamond" | "drop" | "potion" | "money" | "letter" | "face"— 'corpse' (a popped enemy flying), 'fruit', 'points', 'special', 'diamond', 'drop', 'potion', 'money', 'letter', 'face'.enemyKind: string— Enemy kind for corpses.letter: string— Letter for letters.value: number— Points.x: number— Centre x.y: number— Centre y.age: number— Seconds alive.landed: boolean— On the ground.snapped: boolean— Discontinuous move: snap.
CaptureLetters
interface CaptureLetters
Bonus-word letters (EXTEND, SNOW).
word: string[]— The word, one entry per letter position.source: "bubbles" | "creatures"— 'bubbles': letter bubbles float in on rounds with gaps (Bubble Bobble); 'creatures': a face item brings letter creatures (Snow Bros).every: number— Seconds after the previous letter bubble has gone (collected or expired) before the next one floats in.max?: number— Most letter bubbles on screen at once. Default 1.creature?: string— Creature kind for 'creatures'.complete: "extend" | "life"— Completing the word: an extra life and the round ends ('extend'), or just the life ('life').
CapturePatch
type CapturePatch = Partial<Pick<CaptureConfig, "bubbles" | "snow" | "elements" | "scoring" | "items" | "powerups" | "letters" | "anger" | "hurry" | "timing" | "shots" | "rounds" | "loop" | "loopTo" | "invulnerable">> & { player?: Partial<CaptureConfig["player"]> }
The rules configure can change on a running game.
CapturePlayer
interface CapturePlayer
Player rules.
profile: WalkerProfile— Movement profile.lives: number— Starting lives.start: { x: number; feet: number }[]— Default start points (rounds may override).fireEvery: number— Seconds between shots.autoFire: boolean— Holding fire repeats.push: number— Walk speed multiplier while pushing a snowball.
CapturePlayerView
interface CapturePlayerView
A player as read by renderers.
id: string— Body id.index: number— Index (0, 1).score: number— Score.lives: number— Lives left (including the one in play).inGame: boolean— Still in the game.dead: boolean— Dead (death animation).x: number— Centre x.y: number— Centre y.facing: -1 | 1— Facing.mode: WalkerMode— Movement mode.vx: number— Velocity x.vy: number— Velocity y.w: number— Size.h: number— Size.invulnerable: number— Seconds of invulnerability left (blink).carried: boolean— Carried inside a snowball or riding water.balloon: number— Seconds as a balloon left.sinceFire: number— Seconds since the last shot (blow animation).powers: string[]— Power-ups held.letters: boolean[]— Letters collected.pushing: boolean— Pushing a snowball.stunned: boolean— Stunned (burnt).snapped: boolean— Discontinuous move this step: snap the sprite.
CapturePowerup
interface CapturePowerup
Named power-up effects (sweets, shoes, potions). Multipliers apply until the player dies.
range?: number— Bubble or snow range multiplier.shotSpeed?: number— Shot speed multiplier.rapid?: number— Fire interval multiplier (0.5: twice as fast).run?: number— Walk speed multiplier.power?: number— Snow units per hit multiplier.balloon?: number— Seconds as an invincible balloon that flies and kills what it touches (Snow Bros' green potion).
CaptureRound
interface CaptureRound
One round (screen, floor) of a capture game.
name?: string— Display name (e.g. 'ROUND 1', 'FLOOR 1').rows: readonly string[]— Map rows through the legend.legend?: Readonly<Record<string, LegendEntry>>— Legend override for this round.currents?: readonly string[]— Air currents ('^' '>' 'v' '<' '.') for bubbles; default everything up.holes?: { top: number[]; bottom: number[] }— Columns of the gaps in the ceiling and floor (where special and letter bubbles float in).enemies: CaptureSpawn[]— Enemies.start?: { x: number; feet: number }[]— Player start points (feet). Default: the config's.hurry?: number— Seconds before the hurry-up. Default: the config's.chaser?: number— Seconds after the hurry-up before the chaser appears. Default: the config's.noHurry?: boolean— No hurry-up and no chaser on this round, however long it lasts. Default false.chaserSpeed?: number— Chaser speed multiplier. Default 1.enemySpeed?: number— Enemy speed multiplier for this round. Default 1.trapTime?: number— Seconds a trapped enemy holds (bubbles) before the warning. Default: the bubble settings'.floatSpeed?: number— Bubble float speed. Default: the bubble settings'.specials?: { water?: number; fire?: number; lightning?: number }— Special bubbles that float in: rate per element (one every 60 / rate seconds).items?: { points?: { x: number; feet: number }; special?: { x: number; feet: number } }— Where the points item and the special item appear.fruitOnClear?: boolean— Turn leftover bubbles into fruit when the last enemy dies. Default false.breath?: "lightning" | null— Players blow lightning instead of bubbles this round (Bubble Bobble's final round).
CaptureScoring
interface CaptureScoring
Scoring tables.
popChain: number[]— Points for each enemy of a simultaneous pop, in order (Bubble Bobble 1000, 2000, 4000 ...).corpseItems: { item: string; value: number }[]— The fruit the k-th enemy of a pop turns into.emptyPop: number— An empty bubble popped (also a bubble blown into a wall).specialPop: number— A letter or special bubble popped.snowHit: number— A snow shot landing on an enemy.rollChain: number[]— Points for each enemy killed by one kicked snowball chain, in order.diamonds: { water: number; fire: number; lightning: number; trapped: number | null }— Diamond values for enemies killed by water, fire, lightning, and trapped enemies hit by lightning (null: a bolt passes trapped enemies by).drops: { item: string; value: number; weight: number; powerup?: string }[]— Items dropped by enemies killed by snowballs (weighted); an entry withpowerupis a potion.dropChance: number— Chance that a snowball kill drops an item.allInOne: { item: string; value: number; count: number; faceEvery: number } | null— Clearing every enemy with one kick: items rain down (faceEvery-th time a face appears instead).clearFruit: { digits: boolean; value: number } | null— Leftover bubbles become fruit when the last enemy dies (onfruitOnClearrounds, or matching tens and hundreds digits).touchKill: number— Enemy touched by a balloon or a frozen enemy touched.extraLives: number[]— Score thresholds for extra lives, then one everyextraEvery(0 none).extraEvery: number— Points between extra lives after the table.
CaptureSnapshot
interface CaptureSnapshot
Serialisable session state.
core: string— Session scalars, players, items and timers.map: PlatformMapSnapshot— Map.world: WalkerWorldSnapshot— Walker world.enemies: EnemiesSnapshot— Enemy director.bubbles: BubblesSnapshot | null— Bubbles.snow: SnowSnapshot | null— Snow.elements: ElementsSnapshot— Elements.shots: { items: Projectile[]; seq: number }— Projectiles.chasers: { items: Chaser[]; seq: number }— Chasers.
CaptureSpawn
interface CaptureSpawn
An enemy placed in a round.
kind: string— Kind name.x: number— Centre x.feet: number— Feet y.delay?: number— Seconds after the round starts before it drops in. Default 0.facing?: -1 | 1— Facing. Default: toward the screen centre.
CaptureSpecialItem
interface CaptureSpecialItem
A special item earned by a counter reaching a threshold (per difficulty column).
item: string— Item name (renderers draw it).counter: CaptureCounter— The counter.at: [number, number, number, number]— Thresholds for columns AA BB CC DD.effect: CaptureEffect— Effect when collected.value: number— Points when collected.
CaptureState
interface CaptureState
The whole session state for renderers (a fresh copy per read).
status: CaptureStatus— Status.statusTime: number— Seconds in the status.round: number— Round number (1-based).roundName: string— Round display name.roundTime: number— Seconds of play in this round (resets on a death).hurry: boolean— The hurry-up has sounded.hurryTime: number— Seconds since the hurry-up (banner).players: CapturePlayerView[]— Players.enemies: CaptureEnemyView[]— Enemies (not trapped in bubbles).bubbles: Bubble[]— Bubbles.snow: SnowShot[]— Snow shots.shots: Projectile[]— Enemy projectiles.chasers: Chaser[]— Chasers.items: CaptureItemView[]— Items and corpses.water: WaterFlow[]— Water flows.fire: FireDrop[]— Fires.bolts: Bolt[]— Lightning bolts.freeze: number— Seconds left of a freeze (heart).shower: number— Seconds left of a shower bonus (potion).letterAllowance: number— Letters offered this round (bubbles rule).clearing: number— Seconds until the round ends after the last enemy (-1: not clearing).hiScore: number— High score seen this session.mapRevision: number— Map revision (renderers redraw cells when it changes).
CaptureStatus
type CaptureStatus = "ready" | "playing" | "dying" | "clear" | "extend" | "over" | "complete"
Session status.
CaptureTiming
interface CaptureTiming
Timings in seconds.
ready: number— Round intro before play.dying: number— Death freeze.clear: number— After the last enemy before the round ends (collecting fruit).transition: number— Round-to-round transition.extend: number— EXTEND celebration before the next round.respawn: number— Invulnerability after a respawn.entry: number— Seconds an enemy takes to drop into place.corpseGhost: number— Seconds a popped enemy flies through everything before it can land.
PointsPickContext
interface PointsPickContext
What a points-item rule can see: the round, the players, how the previous round went, and the seeded stream.
round: number— Round number, from 1.players: number— Players in the game.previous: { time: number; hurry: boolean; chaser: boolean; umbrella: boolean }— The previous round: seconds it took, whether the hurry-up came, whether a chaser came, whether it was skipped by an umbrella.rng: Rng— The session's seeded stream.


