The pack
grabThrowColumns
function grabThrowColumns(): Pack<{ grabThrowColumns: GrabThrowColumnsApi }>
Install the grab-throw columns API (pure rules, so it needs no backend and runs headless).
Helpers
attackLevel
function attackLevel(cleared: number, chain: number): number
Magical Drop III's attack level (fandom "Gameplay overview/Magical Drop III", after maruta): ⌊(cleared − 3) / 4⌋ + 4 × (chain − 1), never below zero.
attackLines
function attackLines(level: number, table: readonly number[] = MD3_ATTACK_LINES): number
Lines for an attack level from a table (index = level, last entry repeats; an empty table sends nothing).
boardFromColumns
function boardFromColumns(columns: string[], width = columns.length): string[]
Turn columns (top to bottom, one string each) into board rows (top to bottom).
chainBonus
function chainBonus(n: number, scoring: DropScoring = MD3_SCORING): number
The chain bonus for a chain of n links. A single clear (n = 1) earns nothing extra; n = 2..doubleUntil earns
chainBase × 2^(n − 2); beyond that the bonus is linearStep × (n − doubleUntil).
chainScore
function chainScore(cleared: number, chain: number, scoring: DropScoring = MD3_SCORING): number
The score of a whole chain: pieces × perPiece plus the chain bonus.
createDropGame
function createDropGame(config: DropConfig): DropGame
Create a game from a configuration (see the presets).
DROP_DIFFICULTY
const DROP_DIFFICULTY: Readonly<Record<DropDifficultyName, DropDifficulty>>
Easy / Normal / Arcade.
DROP_KINDS
const DROP_KINDS: readonly DropKind[]
Every piece-kind name, for validation.
DROP_LEGEND
const DROP_LEGEND: Readonly<Record<string, DropSpec | null>>
The default legend. Colours: r red 0, b blue 1, y yellow 2, g green 3, p purple 4, c cyan 5; ? a random colour.
Upper case is the special (suit) piece of that colour, $ a random special. * rainbow, # ice,
h j k l m n colour ice (red, blue, yellow, green, purple, cyan), o bubble, ^ v < > arrows, 1–9 bombs.
. and space are empty.
DROP_PUZZLES
const DROP_PUZZLES: readonly DropPuzzle[]
The Drop Puzzle stages: 8 × 10 boards in five colours, after the first game's puzzle mode (clear every drop within a set number of moves). Each teaches one rule of the pack; the last combines them.
DROP_REPEAT
const DROP_REPEAT: DropRepeat
Default auto-repeat. The series moves the clown one column per joystick input with no speed limit of its own ("moving is instantaneous", PureDragon); holding the stick repeats. The repeat timing is not published, so these values are an estimate tuned to feel like the NEOGEO games: a short delay, then 20 columns per second.
DROP_STEP
const DROP_STEP: number
The fixed simulation step, seconds (120 Hz).
dropDifficulty
function dropDifficulty(name: DropDifficultyName = "normal"): DropDifficulty
Look up a difficulty row.
dropPreset
function dropPreset(name: "magical-drop" | "drop-puzzle", options: MagicalDropOptions & DropPuzzleOptions = {}): DropConfig
Preset by name.
dropPuzzlePreset
function dropPuzzlePreset(options: DropPuzzleOptions = {}): DropConfig
Drop Puzzle: a materially different configuration through the same API. An 8 × 10 board, five colours, no natural rows, authored stages cleared within a throw limit, a clown that wraps, and the series' other pieces (arrows, bombs, bubbles, ice, rainbows) placed by hand.
magicalDropLevels
function magicalDropLevels(rules: DropRuleSet = "md3", pushScale = 1): DropLevel[]
The survival level table, 20 rows. The series never published its push intervals, so the speeds are an estimate: 6 s per row at level 1, 10% faster per level, never below 1.2 s (before the difficulty scale). The mix follows Magical Drop II's survival mode as described: specials and rainbows early, then ice and bubbles join while specials and rainbows thin out. MD1 had neither rainbows nor ice; MD3 adds colour ice. About 2% of cells are specials or rainbows at first; that density is also an estimate.
magicalDropPreset
function magicalDropPreset(options: MagicalDropOptions = {}): DropConfig
Magical Drop as a configuration. md3 (the default) is the best-documented rule set: 7 × 12, specials and normals match and grab together, rainbow, ice and colour ice, bubbles, the C-button push, maruta's timings and scoring. md2 keeps specials apart in the hand; md1 also matches them only with each other and uses the first game's taller 7 × 14 field.
MD3_ATTACK_LINES
const MD3_ATTACK_LINES: readonly number[]
Lines sent per attack level, index = level (the last entry repeats). Fitted to maruta's published ranges for the "medium" group of characters (2-chain 3–5 lines, 3-chain 4–5, 4-chain 5–6, 5-chain 6–7, 6-chain 7–8, 7+ chains 8, a single clear 0). The arcade's exact three tables were published only as images.
MD3_SCORING
const MD3_SCORING: DropScoring
Magical Drop III's scoring (maruta, "010 スコアの算出").
MD3_TIMING
const MD3_TIMING: DropTiming
Magical Drop III timing. pop 0.667 s and the rise times are maruta's measurements; throw and grab speeds are read off 20 fps footage (about 9 rows in 0.12 s); pushSlide, garbageLine, ready and clear are presentation estimates.
parseBoard
function parseBoard(c: DropConfig, rows: string[]): DropSpec[][]
Parse an authored board (rows top to bottom) into columns of specs; columns must hang without gaps.
validateDropConfig
function validateDropConfig(c: DropConfig): void
Check a configuration and report the first unsupported setting clearly.
Classes
DropGame
class DropGame
One Magical Drop–style game. Inputs (move, grab, throw, pushRow, receiveGarbage) act immediately;
tick(dt) advances the fixed 120 Hz simulation. Read state for drawing and drain() for events.
readonly config: DropConfig— The configuration in force (as given).constructor(config: DropConfig)— Build a game; throws a clear error for unsupported settings.reset(): void— Start again from the configured start level (same seed sequence continues; use a new game to repeat a seed).goToLevel(level: number): void— Jump to a level or stage (1-based) on a fresh board.retry(): void— Replay the current level or stage on a fresh board (a puzzle retry).levelRow(level = this.level): DropLevel— The level row in force (the last row repeats).stage(level = this.level)— The puzzle stage in force for the clear objective, or null.get busy(): boolean— True while a clear, rise or wipe is in progress: natural rows and garbage wait, and further clears chain.get state(): DropGameState— A copy of the game for presentation.pieceAt(col: number, row: number): DropPieceState | null— The piece at a column and row (0 = top), or null.drain(): DropEvent[]— Take the events since the last drain.move(dir: -1 | 1): boolean— Move the clown one column left (−1) or right (+1); wraps whenclown.wrapis set.moveTo(col: number): boolean— Put the clown under a column directly (a touch tap). Movement in the series is instantaneous.grab(): boolean— Grab the bottom piece in the clown's column and every compatible piece directly above it.throw(): boolean— Throw every held piece up under the clown's column.pushRow(): boolean— Push a row in now (MD3's C button), whenrules.manualPushallows.receiveGarbage(lines: GarbageLine[] | number): number— Queue incoming garbage (versus play). Lines enter from the ceiling one at a time, only while this board is idle (a running chain holds them back, as in the series), up togarbage.capwaiting. A number queues that many full lines of random colours.offsetGarbage(count: number): number— Cancel up tocountwaiting garbage lines, newest first (an offsetting rule for a versus layer). Returns how many.tick(dt: number): DropGameState— Advance bydtseconds (fixed 120 Hz steps inside). Returns the new state.settle(maxSeconds = 30): DropGameState— Run fixed steps until the board is idle (no clears, risings, wipes or flights) ormaxSecondspass.snapshot(): string— A deep, JSON-safe copy of the whole game.restore(snapshot: string): void— Restore asnapshot()taken from a game with the same configuration.
DropRepeater
class DropRepeater
Turns held left/right state into clown steps: one step on the press, then repeats after delay every rate.
The most recently pressed direction wins when both are held.
readonly repeat: DropRepeat— The timing in force.constructor(repeat: DropRepeat = DROP_REPEAT)— Build with a timing (defaultDROP_REPEAT).update(dt: number, left: boolean, right: boolean, pressedLeft = false, pressedRight = false): number— Advance bydtwith the current held state; returns signed steps to take this frame (usually −1, 0 or 1).pressedLeft/pressedRightmark a fresh press this frame (so a tap shorter than a frame still steps).reset(): void— Forget the held state.
Types
ArrowDir
type ArrowDir = "up" | "down" | "left" | "right"
An arrow piece's direction (on screen: up is toward the ceiling).
DropChance
interface DropChance
A chance for a generated cell to be a given piece (instead of a plain colour).
spec: DropSpec— The piece. Acolourof −1 (or absent) on a coloured kind means "a random colour".chance: number— Probability per generated cell, 0..1.
DropConfig
interface DropConfig
The board's dimensions, colours and contents.
cols: number— Columns (MD1–MD3 arcade: 7; Super Famicom: 6).rows: number— Rows of pieces above the clown's line (MD3: 12). A column longer than this loses.colours: number— Normal colours in play (the series: 4).rules: DropRules— Rule variations.timing: DropTiming— Timing.legend: Record<string, DropSpec | null>— Character → piece, for boards, strips and stages.startRows: number— Rows generated at the start of each level (ignored whenboardor a stage gives the board).board: string[] | null— An authored starting board (rows top to bottom), used for every level when set.generator: DropGenerator— Natural row source.levels: { table: DropLevel[]; start: number; every: number }— Level table (the last row repeats), start level and pieces per level-up in endless play (0 = never).objective: DropObjective— The objective.stages: DropStage[]— Puzzle stages for theclearobjective (level n plays stage n).limits: { throws: number; seconds: number; fastPush: number }— Default limits (stages may override): throws (0 = none), seconds (0 = none) and the push interval once time is up.clown: { start: number; wrap: boolean }— Clown start column and whether moving off one edge wraps to the other (MD2 survival).scoring: DropScoring— Scoring.attack: { table: number[]; minChain: number }— Attack output for versus play: lines per attack level and the shortest chain that attacks (the series: 2).garbage: { cap: number }— Most garbage lines that can wait in the incoming queue (MD3: 8).seed: number— Seed for rows, specials and garbage colours.
DropDifficulty
interface DropDifficulty
Difficulty scaling applied by the presets. Arcade is the series' (or the best estimate of it).
pushScale: number— Multiplier on every push interval (higher is slower).levelEvery: number— Pieces cleared per level-up in endless play.quota: number— Quota per level in quota mode.extraThrows: number— Extra throws over par on puzzle stages.puzzleSeconds: number— Puzzle stage time limit in seconds (0 = none).
DropDifficultyName
type DropDifficultyName = "easy" | "normal" | "arcade"
Difficulty names shared by every preset.
DropEvent
type DropEvent = | { kind: "move"; col: number; wrapped: boolean } | { kind: "grab"; col: number; ids: number[]; rows: number[] } | { kind: "grab-refused"; col: number; reason: "empty" | "colour" | "blocked" | "full" | "ungrabbable" } | { kind: "throw"; col: number; ids: number[] } | { kind: "land"; col: number; ids: number[] } | { kind: "clear"; ids: number[]; colour: number; chain: number; count: number; cause: "match" | "wipe" | "arrow" | "bomb" } | { kind: "bubble-pop"; ids: number[] } | { kind: "thaw"; ids: number[]; colour: number; into: DropKind } | { kind: "wipe"; colour: number; chain: number } | { kind: "vanish"; ids: number[] } | { kind: "chain"; chain: number } | { kind: "chain-end"; chain: number; cleared: number; score: number; attack: number } | { kind: "attack"; chain: number; cleared: number; level: number; lines: number } | { kind: "bomb-tick"; id: number; count: number } | { kind: "push"; manual: boolean; ids: number[] } | { kind: "garbage"; ids: number[]; cols: number[] } | { kind: "garbage-queued"; lines: number; pending: number } | { kind: "score"; points: number; total: number } | { kind: "level-up"; level: number } | { kind: "level-start"; level: number } | { kind: "level-clear"; level: number } | { kind: "lost"; cause: DropLoss } | { kind: "complete"; score: number }
Events, in order, drained with drain().
DropGameState
interface DropGameState
A copy of the whole game for presentation.
status: DropStatus— Status.statusTime: number— Seconds in this status.statusRemaining: number— Seconds left in a timed status (ready, clear), else 0.cols: number— Columns.rows: number— Rows.level: number— Level (1-based); for puzzle stages, the stage number.stage: string— The stage's name for theclearobjective, else "".score: number— Score.cleared: number— Pieces cleared this game (bubbles excluded).levelCleared: number— Pieces cleared this level.quotaLeft: number— Pieces still needed (quota objective), else −1.throwsLeft: number— Throws left under a throw limit, else −1.timeLeft: number— Seconds left under a time limit or the time objective, else −1.chain: number— The current chain link while a chain runs, else 0.maxChain: number— Longest chain this game.busy: boolean— Clears, risings or wipes in progress (natural pushes and garbage wait).clown: { col: number; jump: number }— The clown's column and a counter that bumps on wrap-around jumps (snap the sprite).hand: DropHeldState[]— Held pieces, the one nearest the ceiling first.pieces: DropPieceState[]— Pieces on the board.heights: number[]— Column lengths (clocks included).pendingGarbage: number— Garbage lines waiting.pushIn: number— Seconds to the next natural push (−1 with no natural rows).lostCause: DropLoss | null— Why the game was lost, or null.
DropGenerator
type DropGenerator = | { /** Random colours from the seed. */ kind: "random"; /** Probability a cell repeats the colour of the piece it pushes down: higher makes vertical runs commoner. */ repeat: number; } | { /** An authored strip of rows that loops, starting at a random row (the arcade's looping 64-row strips). */ kind: "strip"; /** Rows, top to bottom, `cols` characters each, through the legend. */ rows: string[]; } | { /** No natural rows (puzzle boards). */ kind: "none"; }
Where natural rows come from.
DropHeldState
interface DropHeldState
A piece in the clown's hand.
id: number— Stable id.kind: DropKind— Kind.colour: number— Colour index or −1.dir: ArrowDir | null— Arrow direction, or null.count: number— Bomb count (0).y: number— Displayed row while flying down to the clown; equalsrows(the clown's line) once held.fromCol: number— The column it was taken from (for drawing its flight).
DropKind
type DropKind = "normal" | "special" | "rainbow" | "ice" | "colour-ice" | "bubble" | "arrow" | "bomb"
Piece kinds. See the docs for each kind's grab, match and clear rules.
DropLevel
interface DropLevel
One level row: how fast rows arrive and what they contain.
pushInterval: number— Seconds between natural row pushes while the board is idle.specials?: DropChance[]— Special pieces mixed into generated rows at this level (default none).repeat?: number— Probability that a generated cell repeats the colour of the piece it pushes down (default: the generator's).
DropLoss
type DropLoss = "push" | "garbage" | "throw" | "limit" | "time"
Why a game was lost.
DropObjective
type DropObjective = | { /** Survive: levels advance every `levels.every` pieces; the game ends only when the stack overflows. */ kind: "endless"; } | { /** Clear `count` pieces to finish the level; the next level starts on a fresh board (the versus quota rule played solo). */ kind: "quota"; /** Pieces to clear per level. */ count: number; } | { /** Clear as many pieces as possible before the clock runs out ("Time Crush"). */ kind: "time"; /** Length of the game in seconds. */ seconds: number; } | { /** Clear every piece from an authored board (puzzle stages). */ kind: "clear"; }
The round objective.
DropPhase
type DropPhase = "rest" | "rise" | "throw" | "push" | "clearing"
A piece's presentation phase.
DropPieceState
interface DropPieceState
A piece on the board. Ids are stable for the piece's whole life (board → hand → board).
id: number— Stable id.kind: DropKind— Kind.colour: number— Colour index, or −1 for colourless kinds.dir: ArrowDir | null— Arrow direction, or null.count: number— Bomb count (0 for other kinds).col: number— Column.row: number— Logical row from the ceiling (0 = top).y: number— Displayed row (float): equalsrowat rest; between rows while rising, flying or sliding in.phase: DropPhase— What it is doing.clear: number— Clearing progress 0..1 (0 unless clearing).chain: number— The chain link that cleared it (0 unless clearing).silent: boolean— A bubble popping silently (no score) rather than a scored clear.
DropPuzzle
interface DropPuzzle extends DropStage
A Drop Puzzle stage with its par (the fewest throws that clear it).
name: string— Display name.par: number— Fewest throws that clear the board.solution: [number[], number][]— One solution: pairs of [grab column(s), throw column], for tests and hints.
DropPuzzleOptions
interface DropPuzzleOptions
Options for dropPuzzlePreset.
difficulty?: DropDifficultyName— Easy (+2 throws, no clock), Normal (+1, 120 s) or Arcade (par, 60 s).stage?: number— First stage (1-based).stages?: readonly DropPuzzle[]— Stages to play (defaultDROP_PUZZLES).cols?: number— Columns (default 8).rows?: number— Rows (default 10).wrap?: boolean— Clown wraps edge to edge (default true).
DropRepeat
interface DropRepeat
Auto-repeat timing for a held direction.
delay: number— Seconds a direction must be held before it repeats.rate: number— Seconds between repeats after that.
DropRules
interface DropRules
Rule variations across the series.
matchLength: number— Pieces in a vertical line needed to clear (the series: 3).specialMatch: "separate" | "with-normals"—separate: special pieces only match other specials (MD1).with-normals: they match normals of their colour (MD2+).specialGrab: "separate" | "with-normals"—separate: specials cannot be held with normals (MD1, MD2).with-normals: grabbed together (MD3).bubblePopOnGrab: boolean— Grabbing a bubble pops it (Saturn/PS arrangements and later); otherwise bubbles block the column (NEOGEO).manualPush: boolean— The player may push a row in at any time (MD3's C button).throwOverflow: "lose" | "refuse"— A throw that would push the column past the bottom:lose(the series) orrefuse.thawKeepsKind: boolean— A special or rainbow clear that takes in ice turns all plain ice into specials/rainbows (MD3); otherwise normals.handLimit: number— Most pieces the clown may hold (0 = unlimited, as in the series).
DropRuleSet
type DropRuleSet = "md1" | "md2" | "md3"
Series rule sets.
DropScoring
interface DropScoring
How a finished chain scores. Defaults are Magical Drop III's, measured by maruta (see the docs).
perPiece: number— Points per piece cleared during the chain (MD3: 2). Bubbles that pop silently score nothing.chainBase: number— Bonus for a 2-chain (MD3: 24); each further link doubles it up todoubleUntil.doubleUntil: number— The last chain length that doubles (MD3: 13, worth 24 × 2^11).linearStep: number— PastdoubleUntil, each extra link is worth this many more points (MD3: 50,000 × (n − 13)).
DropSpec
interface DropSpec
A piece description, as authored in maps, strips, generators and garbage lines.
kind?: DropKind— The kind; defaultnormal.colour?: number— Colour index 0..colours−1 for normal, special and colour-ice; ignored (−1) for the others.dir?: ArrowDir— Arrow direction (arrows only).count?: number— Bomb count (bombs only, ≥ 1).
DropStage
interface DropStage
An authored puzzle stage for the clear objective.
name?: string— Display name.board: string[]— Rows, top to bottom, through the legend. Columns must hang from the ceiling without gaps.throws?: number— Throw limit for this stage (0 = unlimited). Overrideslimits.throws.seconds?: number— Time limit in seconds (0 = none). Overrideslimits.seconds.
DropStatus
type DropStatus = "ready" | "playing" | "clear" | "lost" | "complete"
Game status.
DropTiming
interface DropTiming
Timing, in seconds or rows per second.
pop: number— How long a cleared piece stays as a flashing "clock" before vanishing (MD3: 0.667 s, fixed).riseBase: number— Fixed part of the time pieces take to rise into a gap (MD3: about 0.2 s).risePerRow: number— Extra rise time per row of gap (MD3: about 0.05 s; 1 row 0.25 s, 3 rows 0.35 s, 5 rows 0.45 s).throwSpeed: number— Thrown pieces fly up at this many rows per second (MD3 footage: about 9 rows in 0.12 s).grabSpeed: number— Grabbed pieces fly down to the clown at this many rows per second (presentation only).pushSlide: number— Time a pushed row takes to slide in (presentation only; the push itself is instant).garbageLine: number— Seconds between incoming garbage lines while they enter.ready: number— READY time before play.clear: number— Time the level-clear banner shows before the next level or stage.
GarbageLine
type GarbageLine = (DropSpec | null)[]
One garbage line: a piece per column, or null where that column is not pushed (MD3's uneven attack shapes).
GrabThrowColumnsApi
type GrabThrowColumnsApi = typeof api
game.grabThrowColumns: every function, class and type this pack carries, as one namespace.
MagicalDropMode
type MagicalDropMode = "endless" | "quota" | "time"
Single-player modes of the Magical Drop preset.
MagicalDropOptions
interface MagicalDropOptions
Options for magicalDropPreset.
mode?: MagicalDropMode— endless (survival), quota (levels cleared by quota on fresh boards) or time (clear the most in the time).rules?: DropRuleSet— Rule set: md1 (1995), md2 (1996) or md3 (1997, default).difficulty?: DropDifficultyName— Easy, Normal (default) or Arcade.level?: number— Start level (default 1).seed?: number— Seed.cols?: number— Columns (default 7).rows?: number— Rows (default 12; 14 for md1).colours?: number— Colours (default 4).startRows?: number— Rows on the board at the start of a level (default 5).wrap?: boolean— Clown wraps from edge to edge (default: md2's survival field does; others do not).quota?: number— Quota per level (quota mode; default from the difficulty).seconds?: number— Game length in seconds (time mode; default 120).levelEvery?: number— Pieces per level-up in endless mode (default from the difficulty).pushScale?: number— Override the push-interval scale (default from the difficulty).


