The pack
faller
function faller(): Pack<{ faller: FallerApi }>
The faller pack. Pure logic, so it needs no backend and runs headless.
Helpers
capsuleRotation
function capsuleRotation(): RotationSystem
Dr. Mario capsules: two halves in a 2x2 box whose bottom-left cell is always filled. States: 0 = horizontal [a b], 1 = vertical a over b, 2 = horizontal [b a], 3 = vertical b over a (clockwise order). A turn to horizontal that is blocked on the right kicks one column left (NES); a turn to vertical has no kick. Halves carry links so they stay joined after locking until one of them is cleared. Spawn: horizontal, left half at column floor(cols / 2) - 1 (columns 4-5 of 8), row 0.
COLUMNS_GRAVITY_FRAMES
const COLUMNS_GRAVITY_FRAMES: readonly [40, 34, 29, 25, 21, 18, 15, 13, 11, 9, 8, 7, 6, 5, 4, 3, 2]
Columns frames per row by level (est.: no published table was found; tuned to the arcade's pace).
COLUMNS_JEWELS_PER_LEVEL
const COLUMNS_JEWELS_PER_LEVEL: { readonly easiest: 50; readonly easy: 40; readonly normal: 35; readonly hard: 25; }
Jewels per level by the arcade's difficulty DIP switch (Sega "Columns Conversion" manual, Figure 9).
COLUMNS_START
const COLUMNS_START: { readonly 0: 0; readonly 5: 20000; readonly 10: 50000; }
The arcade's start choices (KLOV screenshot of the level select): level and starting bonus.
columnsArcade
function columnsArcade(opts: ColumnsOptions = {}): FallerRules
Columns (Sega 1990): three-jewel columns that fall in half-row steps and cycle their order with the button; matches of three or more in rows, columns and diagonals; cascades scored 30 x step x lines-of-three x (level + 1); a level every 35 jewels (DIP); the start-level bonus; the magic jewel that clears every jewel of the colour it lands on; game over when jewels are left above the well.
columnsScoring
function columnsScoring(): ScoringFn
Columns scoring, after the Mega Drive analysis on TASVideos (forum topic 5090): each chain step scores 30 x step x lines-of-three x (level + 1), where a run of four is two lines of three and five is three. Soft drop scores (level + 1) per half row, i.e. 2 x (level + 1) per row. A magic-jewel clear has no documented value; this scores 10 x (level + 1) per jewel, the per-jewel rate of a line of three (an estimate, noted in the docs).
CRASH
const CRASH: 100
Colour offset that marks a crash gem: a crash gem of colour c is stored as CRASH + c.
CRASH_DAMAGE
const CRASH_DAMAGE: CrashDamage
Damage as estimated by players of the original (no published formula exists): a broken gem is worth one counter gem, a power gem cell 1.25 (a 2 x 2 power gem and its crash gem send about six), each gem in chain step n counts n times, a destroyed counter gem 0.5, and the diamond's break is halved (HD Remix: 80 %).
crashGemDeal
function crashGemDeal(r: CrashGemRules): PieceSpec[]
Builds the pre-generated drop order: both boards on one seed get the same pairs (as in the original).
crashGemFallerRules
function crashGemFallerRules(r: CrashGemRules): FallerRules
The FallerRules a crash-gem board runs its pair with (matching off; this module resolves moves).
createCrashGems
function createCrashGems(rules: CrashGemRules): CrashGemGame
Creates a crash-gem board. Throws a RangeError for an unsupported configuration.
createFaller
function createFaller(rules: FallerRules): FallerGame
Creates a game from a ruleset.
cycleRotation
function cycleRotation(length = 3, spawnColumn?: (cols: number) => number): RotationSystem
Columns: a vertical column of length jewels (origin = the bottom jewel). Rotating does not move the
footprint; it cycles the colours (clockwise moves every jewel down one place and the bottom one to the top).
A magic column does not cycle.
deal
function deal(rule: DealRule, memory: DealMemory, rng: Rng): PieceSpec
Deals the next piece by rule, updating memory.
dealMemory
function dealMemory(): DealMemory
A fresh dealer memory.
DIAGONAL_SEND_RATE
const DIAGONAL_SEND_RATE = (raw: number): number=> …
The original's send rate for the two diagonal patterns: floor(x / 4) + floor(x / 2), about 75 %.
DIAMOND
const DIAMOND: 200
Colour code of the diamond.
DR_MARIO_BASE
const DR_MARIO_BASE: readonly [100, 200, 300]
Dr. Mario base points per virus by speed: LOW, MED, HI.
DR_MARIO_GRAVITY_FRAMES
const DR_MARIO_GRAVITY_FRAMES: readonly [70, 68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1]
Dr. Mario NES NTSC frames per row by speed index 0..80 (https://tetris.wiki/Dr._Mario).
DR_MARIO_MULTIPLIERS
const DR_MARIO_MULTIPLIERS: readonly [1, 2, 4, 8, 16, 32]
Dr. Mario per-virus multipliers for the 1st..6th+ virus of one move (https://www.mariowiki.com/Dr._Mario_(game)).
DR_MARIO_SPEED_START
const DR_MARIO_SPEED_START: readonly [15, 25, 31]
Starting speed index for LOW, MED and HI.
drMario
function drMario(opts: DrMarioOptions = {}): FallerRules
Dr. Mario (Nintendo 1990): 8 x 16 bottle, viruses placed by the NES algorithm (count 4 x (level + 1), max 84; rows by level; the distance-two colour rule), capsules from a 128-entry loop of the nine colour pairs, the 2x2 rotation with the left kick, four in a row or column clears (viruses included), unsupported halves fall at four rows a second, LOW/MED/HI speeds with a speed-up every ten capsules, scoring by viruses per move, level clear and top-out when the bottle's neck is blocked.
drMarioScoring
function drMarioScoring(): ScoringFn
Dr. Mario scoring: only viruses score. The k-th virus cleared in one move (a capsule and every cascade it causes) is worth base x 1, 2, 4, 8, 16, 32 (sixth and later), with base 100 / 200 / 300 for LOW / MED / HI.
DROP_PATTERNS
const DROP_PATTERNS: Readonly<Record<string, DropPattern>>
The eleven drop patterns of Super Puzzle Fighter II Turbo (1996), read from the original-pattern charts in David
Sirlin's "Balancing Puzzle Fighter" (2014; he rebalanced the game for HD Remix and published the old patterns),
cross-checked against the row layout in the X' rebalance patch source (github.com/KScl/puzzle-fighter-x-prime).
Keys are neutral descriptions; source names the fighter that used the pattern.
GEM_LETTERS
const GEM_LETTERS: Readonly<Record<string, number>>
Colour letters used by drop patterns: R G B Y = 0 1 2 3 (the original's red, green, blue, yellow).
gemColor
function gemColor(c: Cell): number
The base colour of a cell (crash gems and counter gems included); -1 for the diamond.
gemPairRotation
function gemPairRotation(spawnColumn: (cols: number) => number): RotationSystem
The pair rotation of a crash-gem board: Puyo-style orbit with wall and floor kicks, except that a vertical pair wedged in a one-wide well flips 180 degrees on a single press (the original; Sirlin 2014, "the diamond trick").
gemType
function gemType(c: Cell): GemType
The gem type of a cell.
guidelineAttack
function guidelineAttack(e: LinesEvent): number
Lines of garbage sent by a Guideline line clear (the common Guideline attack table): single 0, double 1, triple 2, tetris 4; T-spin single 2, double 4, triple 6 (mini: 0 / 1); +1 back-to-back; combo bonus by count 0,1,1,2,2,3,3,4,4,4,5. A versus layer can supply its own table; the faller pack only reports it.
guidelineGravity
function guidelineGravity(level: number): number
Guideline Marathon gravity (https://tetris.wiki/Marathon): (0.8 - (level - 1) x 0.007)^(level - 1) seconds per row, as rows per 60 Hz frame; 20G (instant) from level 20.
guidelineScoring
function guidelineScoring(opts: GuidelineScoringOptions = {}): ScoringFn
Tetris Guideline scoring (https://tetris.wiki/Scoring): single 100, double 300, triple 500, tetris 800; T-spin mini 100 (no lines) / 200 (single) / 400 (double); T-spin 400 / 800 / 1200 / 1600; all times the level BEFORE the clear. Difficult clears in a row earn x1.5 (back-to-back); combos add 50 x combo x level; soft drop 1 per row, hard drop 2 per row (never multiplied).
NES_GRAVITY_FRAMES
const NES_GRAVITY_FRAMES: readonly [48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1]
NES Tetris NTSC frames per row by level 0..29 (https://tetris.wiki/Tetris_(NES,_Nintendo)).
nesAre
function nesAre(ctx: TimingContext): number
NES entry delay: 10 frames for a lock in the bottom two rows, 2 more for each 4 rows above, up to 18.
nesScoring
function nesScoring(): ScoringFn
NES Tetris scoring (https://tetris.wiki/Scoring, meatfighter's ROM analysis): 40 / 100 / 300 / 1200 x (level + 1), using the level AFTER the clear is counted, plus push-down points (rows of the final soft drop, not multiplied).
NRS_SHAPES
const NRS_SHAPES: Record<string, XY[][]>
NES (Nintendo Rotation System) orientations, relative to the pivot, from the orientation table in
meatfighter's analysis of the ROM. Each list is in CLOCKWISE order; NRS_SPAWN gives the spawn index.
NRS_SPAWN
const NRS_SPAWN: Record<string, number>
NES spawn orientations (Td, Jd, Zh, O, Sh, Ld, Ih) as indices into NRS_SHAPES.
nrsRotation
function nrsRotation(): RotationSystem
The Nintendo Rotation System of NES Tetris: pivot rotation, right-handed, no kicks, spawn at column 5, row 0.
nuisanceFromScore
function nuisanceFromScore(score: number, targetPoints = 70, carry = 0): { nuisance: number; carry: number }
Puyo nuisance from a chain score (https://puyonexus.com/wiki/Scoring): NP = score / targetPoints + carry; the whole part is sent, the fraction carries to the next chain. Target points are 70 in Tsu.
pairRotation
function pairRotation(spawnColumn?: (cols: number) => number): RotationSystem
Puyo pairs: the axis puyo stays put and the child orbits it. A turn into a wall or another puyo pushes the pair
one cell away; a turn into the floor lifts it one row. A vertical pair wedged between walls cannot turn; a
second press then flips it 180 degrees (the quick turn), lifting it if the child would go into the floor.
Colours are [axis, child]. spawnColumn defaults to the third column of six (floor((cols - 1) / 2)).
placeViruses
function placeViruses(field: Field, rng: Rng, level: number, rule: VirusRule): number
Places Dr. Mario viruses by the NES algorithm (https://tetris.wiki/Dr._Mario): pick a row up to the level's maximum and a column; colour = remaining % 4 (0 yellow, 1 red, 2 blue; 3 = random from the 16-entry table); step right (wrapping up a row) past occupied cells and past cells whose distance-two neighbours already hold all three colours; change the colour (yellow -> blue, red -> yellow, blue -> red) until no distance-two neighbour shares it. Rows count from 1 at the bottom. Returns the number placed.
PUYO_COLOR_BONUS
const PUYO_COLOR_BONUS: readonly [0, 3, 6, 12, 24, 48]
Puyo colour bonus by colours cleared in one step (1..6), Tsu (https://puyonexus.com/wiki/Scoring).
PUYO_GRAVITY_FRAMES
const PUYO_GRAVITY_FRAMES: readonly [36, 30, 26, 22, 19, 16, 14, 12, 10, 8, 7, 6, 5, 4, 3, 2]
Puyo frames per row by level (est.: endless is not a Tsu mode; this is a gentle curve from Tsu's opening pace).
PUYO_GROUP_BONUS
const PUYO_GROUP_BONUS: readonly [0, 2, 3, 4, 5, 6, 7, 10]
Puyo group bonus by group size 4..11+ (https://puyonexus.com/wiki/Scoring).
PUYO_LETTERS
const PUYO_LETTERS: Record<string, number>
Letter-to-colour map for Puyo layouts: R, G, B, Y, P.
PUYO_MISSIONS
const PUYO_MISSIONS: readonly PuyoMission[]
Authored missions (original puzzles in the nazo-puyo style; solvable, verified by the tests).
PUYO_TSU_CHAIN_POWER
const PUYO_TSU_CHAIN_POWER: readonly [0, 8, 16, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672]
Puyo Puyo Tsu chain power for chains 1..24 (https://puyonexus.com/wiki/Chain_Power_Table); 24+ stays at 672...
puyoStepScore
function puyoStepScore(chain: number, groups: readonly number[], colors: number, popSize = 4): number
The Puyo Puyo Tsu score of one chain step: 10 x PC x clamp(CP + CB + GB, 1, 999). popSize is the minimum group (4).
puyoTsu
function puyoTsu(opts: PuyoOptions = {}): FallerRules
Puyo Puyo Tsu single-player field rules: 6 x 12 with a hidden 13th row (it holds puyos but never pops) and a 14th
row where puyos vanish; pairs that orbit their axis with wall and floor kicks and the double-press quick turn;
groups of four or more pop, nuisance next to a pop clears, cascades score 10 x PC x (chain power + colour bonus +
group bonus) with Tsu's tables; all clears; the X in the third column ends the game. Nuisance (versus) follows
Tsu: 30 at most per move, whole rows first then random distinct columns, 70 target points, +30 after an all clear.
Modes: endless (speed rises every piecesPerLevel pieces: a house mode, Tsu has none) and missions.
puyoTsuScoring
function puyoTsuScoring(popSize = 4): ScoringFn
Puyo Puyo Tsu scoring: chain steps by puyoStepScore; soft drop 1 per row.
puzzleFighter
function puzzleFighter(opts: PuzzleFighterOptions = {}): CrashGemRules
Super Puzzle Fighter II Turbo (Capcom, 1996) board rules. From the PlayStation manual (Capcom): pairs of gems drop from the top centre; a crash gem breaks the gems of its colour linked to it; four or more same-coloured gems in a rectangle form a power gem; counter gems start at 5 and turn into normal gems at 0, counting down as each pair falls; counter gems that still fall after some are countered start at 3; counter gems touching a break are destroyed whatever their colour; the rainbow gem (diamond) breaks every gem of one colour for fewer counter gems; the round is lost when gems pile into the gap where new gems fall. From Wikipedia and Sirlin (2014): a 6 x 12 board whose fourth column takes the new pair; the diamond every 25 pairs; diamond damage 50 % (HD Remix 80 %); the 70 % (ROM: floor(x/4) + floor(x/2)) send rate of the two diagonal patterns; one press flips a pair wedged in a one-wide well. Estimates (no published figures): the damage values above, the crash gem rate (1 in 8 gems), the drop speed table, the lock delay, flash and fall timings, the columns a partial counter row lands in (random), and the score (10 a gem x chain step).
SRS_KICKS_I
const SRS_KICKS_I: Record<string, readonly (readonly [number, number])[]>
SRS wall kicks for the I piece, same convention as SRS_KICKS_JLSTZ.
SRS_KICKS_JLSTZ
const SRS_KICKS_JLSTZ: Record<string, readonly (readonly [number, number])[]>
SRS wall kicks for J, L, S, T and Z, keyed from>to (0, 1 = R, 2, 3 = L), in the wiki's convention
(+x right, +y UP). Five tests, tried in order.
SRS_SHAPES
const SRS_SHAPES: Record<string, XY[][]>
SRS shapes, per kind, per rotation state (0 = spawn, R, 2, L), relative to the top-left of the bounding box.
srsRotation
function srsRotation(): RotationSystem
The Super Rotation System (Tetris Guideline): SRS shapes, spawn centred (rounded left) in rows 21-22, and the full five-test kick tables, I piece included. 180-degree rotation is not part of SRS: it is two 90-degree turns' worth of state with only the plain test, and the engine offers it only when a ruleset enables it.
tableRotation
function tableRotation(opts: TableRotationOptions): RotationSystem
Builds a rotation system from shape tables (and optional kick tables): SRS, NRS or your own pieces.
tetrisGuideline
function tetrisGuideline(opts: GuidelineOptions = {}): FallerRules
Tetris by the Guideline: SRS with full kicks, 7-bag, hold, a next queue, ghost, 0.5 s lock delay with 15 move resets (restored on reaching a new lowest row), soft drop at 20x gravity (1 point a row), hard drop (2 points a row), T-spins (3-corner, mini by the pointing side, upgraded by the fifth kick), back-to-back x1.5, combos, the Marathon gravity curve, 10 lines a level, block-out / lock-out top-out, and a 20-row buffer above the well.
tetrisNes
function tetrisNes(opts: NesOptions = {}): FallerRules
NES Tetris (Nintendo 1989): the Nintendo Rotation System (right-handed, no kicks), no hold, no hard drop, no ghost, the NES gravity table, DAS 16/6 (charge kept between pieces, frozen during ARE, charged by a wall, shifting ignored while Down is held), soft drop at 1/2G after 3 frames that must be re-pressed for each piece, no lock delay, ARE 10-18 by lock height plus 17-20 frames for a line clear, 40/100/300/1200 x (level + 1) scoring plus push-down points, the NES level-up rule, the reroll randomiser, and a 999,999 score cap.
TETROMINOES
const TETROMINOES: readonly ["I", "O", "T", "S", "Z", "J", "L"]
The seven tetromino kinds in Guideline colour order: I cyan, O yellow, T purple, S green, Z red, J blue, L orange.
Classes
CrashGemGame
class CrashGemGame
A crash-gem board (Super Puzzle Fighter II Turbo rules). Drive it with frame(input) at 60 Hz; read the cells,
power gems, counter timers and the falling pair; drain events. Versus hooks: queueCounters, offsetCounters,
pendingCounters, and the crash / moveEnd events' attack.
readonly rules: CrashGemRules— The ruleset.readonly faller: FallerGame— The pair engine (its well is this board's well).constructor(rules: CrashGemRules)— Creates a board.reset(): void— Starts the board over with the same rules and seed.get phase(): CrashGemPhase— Resolution phase ('piece' while a pair is in play or about to be).get phaseProgress(): number— Progress through the current resolution phase, 0..1.get piecePhase(): string— The pair engine's phase ('ready', 'are', 'active', ...).get frameCount(): number— Frames since the board started.get score(): number— Score.get chain(): number— Current chain step (0 when none).get bestChain(): number— Best chain this round.get pendingCounters(): number— Counter gems waiting to fall here.get pendingBatches(): readonly CounterBatch[]— The batches waiting (count, pattern, short countdown).get over(): boolean— True once the round is lost.get reason(): string— Why it ended.get busy(): boolean— True while a move is resolving (breaks, falls, counter gems landing).get active(): FallerGame['active']— The falling pair (colours encode the gem type; seegemType), or null.next(): PieceSpec[]— The next pair(s).cell(x: number, y: number): Cell | null— The cell at (x, y) (row 0 = top visible row).counterTimer(id: number): number | undefined— The countdown of a counter gem (by cell id), or undefined.powerOf(id: number): number | undefined— The power gem a cell belongs to (by cell id), or undefined.get flashing(): readonly XY[] | null— Gems flashing before they break.get falling(): ReadonlyMap<number, number> | null— Cells falling: id -> rows still to fall at the start of this step (multiply by 1 - phaseProgress).powerGems(): PowerGem[]— Every power gem on the board.drain(): CrashGemEvent[]— Removes and returns the pending events.queueCounters(count: number, pattern: DropPattern = this.rules.pattern): void— Queues counter gems from an attacker with its drop pattern. They fall after this board's next move resolves.offsetCounters(count: number): number— Cancels up tocountwaiting counter gems (newest first); the survivors start at the shorter countdown. Returns how many were cancelled.snapshot(): CrashGemSnapshot— A deep copy of the whole board.restore(snap: CrashGemSnapshot): void— Restores a snapshot taken from a board with the same rules.frame(input: FallerInput = {}): void— Advances one 60 Hz frame.placements(): { x: number; rot: number }[]— Every distinct final drop of the falling pair (see FallerGame.placements).place(x: number, rot: number): boolean— Teleports the pair to a placement and drops it (bots and tests). The move resolves on later frames.settleMove(maxFrames = 20000): void— Runs frames with no input until the next pair is in play or the round ends (bounded).preview(x: number, rot: number): CrashGemPreview— What placing the pair at (x, rot) would do, without changing this board: counter gems sent, chain, gems broken, whether the round would end, and the resulting board.
FallerGame
class FallerGame
A falling-piece game. Drive it with frame(input) at 60 Hz; read its state; drain its events.
readonly rules: FallerRules— The ruleset (treat as read-only; build a new game to change it).readonly field: Field— The well.constructor(rules: FallerRules)— Creates a game. Throws a RangeError describing any unsupported configuration.reset(): void— Starts the game over with the same rules and seed.get phase(): FallerPhase— Current phase.get phaseProgress(): number— Progress through the current phase, 0..1.get frameCount(): number— Frames since the game started.get score(): number— Score.get level(): number— Level.get lines(): number— Lines cleared (rows games).get cleared(): number— Cells cleared (Columns jewels, Puyo puyos, Dr. Mario cells).get pieces(): number— Pieces locked.get speedIndex(): number— Speed index (Dr. Mario).get viruses(): number— Viruses left (Dr. Mario).get chain(): number— Current chain step during a cascade (0 when none).get bestChain(): number— Best chain this game.get combo(): number— Combo counter (-1 = none).get backToBack(): boolean— True when the next difficult clear earns back-to-back.get held(): PieceSpec | null— Held piece.get holdUsed(): boolean— Hold already used for this piece.get pendingNuisance(): number— Nuisance waiting to fall.get stats(): Readonly<Record<string, number>>— Pieces dealt by kind.get over(): boolean— True once the game has ended either way.get reason(): string— Why the game ended ('' while running).get goalProgress(): number— Best progress toward the objective (chain reached, lines, score).get targetPoints(): number— Target points used for nuisance (Puyo). Sets target points (a versus layer lowers them during margin time).set targetPoints(v: number)— Target points used for nuisance (Puyo). Sets target points (a versus layer lowers them during margin time).next(): PieceSpec[]— The upcoming pieces,rules.previewsof them.cell(x: number, y: number): Cell | null— The cell at (x, y) (row 0 = top visible row).get active(): ActiveView | null— The falling piece, or null.ghost(): XY[]— Where the falling piece would land.get clearing(): { cells: XY[]; rows: number[]; magic: boolean } | null— Cells being cleared (flashing), with the rows for a rows game.get falling(): ReadonlyMap<number, number> | null— Cells currently falling in a cascade: id -> rows still to fall at the start of this step (multiply by 1 - phaseProgress for the draw offset).drain(): FallerEvent[]— Removes and returns the pending events.snapshot(): FallerSnapshot— A deep copy of the whole game state (events excluded). Restore withrestore.restore(snap: FallerSnapshot): void— Restores a snapshot taken from a game with the same rules.queueNuisance(count: number): void— Queues nuisance to fall after the player's next move resolves (Puyo rules: at mostmaxPerDropper move).offsetNuisance(count: number): number— Cancels up tocountpending nuisance (offsetting). Returns how many were cancelled.queueGarbage(rows: number, hole?: number): void— Queues garbage rows (rows games) that rise after the next piece locks without clearing lines.frame(input: FallerInput = {}): void— Advances one 60 Hz frame.advance(seconds: number, input: FallerInput = {}): void— Advancessecondsof game time at 60 Hz with the same held input (edges apply to the first frame).placements(): { x: number; rot: number }[]— Every final placement of the falling piece reachable by rotating at spawn height and dropping (a bot's move list). For cycling pieces,rotcounts colour rotations.place(x: number, rot: number): boolean— Puts the falling piece at a placement and drops it there: a teleport for bots and tests (no scoring for the movement). Returns false when the placement does not fit. The move then resolves on later frames.settleMove(maxFrames = 20000): void— Runs frames with no input until the next piece is in play or the game ends (bounded).preview(x: number, rot: number): PlacementPreview— What placing the current piece at (x, rot) would do, without changing this game: points, chains, attack, lines and whether the game would end. The AI search surface for a versus opponent.
Field
class Field
The well grid. Rows run from -hidden (top of the buffer) to rows - 1 (bottom); row 0 is the top visible row.
readonly cols: number— Width in cells.readonly rows: number— Visible height in cells.readonly hidden: number— Buffer rows above the visible well.cells: (Cell | null)[]— Cells, row-major from the top of the buffer. Exposed for snapshots; useget/set.nextId: number— The next id to hand out.constructor(cols: number, rows: number, hidden: number)— Creates an empty well.inside(x: number, y: number): boolean— True when (x, y) is inside the well, buffer included.get(x: number, y: number): Cell | null— The cell at (x, y), or null when empty or outside.set(x: number, y: number, cell: Cell | null): void— Writes a cell (null clears). Ignored outside the well.put(x: number, y: number, color: number, kind: CellKind = 'block', link?: Link): Cell— Creates and places a new cell, returning it.fits(cells: readonly XY[]): boolean— True when every cell is inside the well and empty.each(fn: (cell: Cell, x: number, y: number) => void): void— Callsfnfor every occupied cell.count(kind?: CellKind): number— Number of occupied cells, optionally of one kind.top(): number— The highest occupied row, orrowswhen the well is empty.clear(): void— Empties the well.fullRows(): number[]— Rows that are completely filled, top to bottom.collapseRows(rows: readonly number[]): void— Removes the given rows and drops everything above them (Tetris "naive" gravity).lineMatches(n: number, diagonals: boolean, minY = -this.hidden): LineMatch— Runs ofnor more same-coloured cells horizontally and vertically (and diagonally when asked), from rowminYdown. Nuisance and garbage never match. Viruses match like blocks (Dr. Mario).floodMatches(n: number, minY = -this.hidden): MatchGroup[]— Orthogonally connected same-coloured groups ofnor more blocks at or below rowminY(Puyo).adjacentNuisance(cells: readonly XY[], minY = -this.hidden): XY[]— Nuisance cells orthogonally next to any ofcells, at or belowminY(they clear with a popping group).remove(cells: readonly XY[]): Cell[]— Removes cells. A capsule half whose partner is removed becomes a free half. Returns the removed cells.gravityStep(mode: 'cells' | 'linked'): number[]— One step of gravity: every unsupported unit falls one row.cellsmode moves each cell on its own;linkedmode keeps joined capsule halves together. Viruses never fall. Returns the ids that moved.unsupported(mode: 'cells' | 'linked'): boolean— True when some unit would fall in agravityStep.dropNuisance(count: number, rng: Rng, ceiling = -this.hidden): { id: number; x: number; y: number; rows: number }[]— Dropscountnuisance into the well by the Puyo rules: whole rows ofcolsfirst, the remainder into distinct columns chosen at random. Each nuisance stacks on its column; any that would land above rowceilingvanishes. Returns each placed cell with how far it fell from just above the buffer.raiseGarbage(rows: number, hole: number): boolean— Pushes the stack up byrowsand fills the bottom with garbage rows holed athole. Returns true if anything was pushed out of the top of the buffer (a top-out).
Types
ActiveView
interface ActiveView
The falling piece as the renderer sees it.
kind: string— Piece kind.rot: number— Rotation state.x: number— Origin column.y: number— Origin row.sub: number— Sub-row progress towards the next row (0..1), for smooth drawing of half-row systems.blocks: { x: number; y: number; color: number; link?: PieceBlock['link'] }[]— Absolute blocks.magic: boolean— Magic column (Columns).serial: number— Unique per piece; changes when a new piece appears (snap sprites on change).grounded: boolean— True while resting on the stack.lockProgress: number— Lock-delay progress 0..1 (for a lock flash).
Cell
interface Cell
A locked cell. id is stable for the cell's lifetime (it survives falls), so a renderer can key sprites by it.
id: number— Stable identity for this cell while it exists.color: number— Colour index (-1 for colourless cells such as nuisance).kind: CellKind— What the cell is.link?: Link— The capsule half this one is still joined to.
CellKind
type CellKind = 'block' | 'virus' | 'nuisance' | 'garbage'
What a locked cell is. Viruses never fall; nuisance never matches on its own; garbage is Tetris filler.
ChainEvent
interface ChainEvent
One step of a cascade in a matching game (Columns, Dr. Mario, Puyo).
type: 'chain'— Event kind.chain: number— Chain step, 1 for the first clear of a move.cleared: number— Coloured cells cleared in this step (nuisance excluded).colors: number— Distinct colours cleared in this step.groups: number[]— Size of each group that popped (flood matching) or each colour set (line matching).linesOfN: number— Lines-of-N counted in this step (line matching: a run of four is two lines of three).viruses: number— Viruses cleared in this step.virusesBefore: number— Viruses already cleared earlier in this move.magic: boolean— True when the step was a Columns magic-jewel clear.level: number— Current level.speed: number— Speed setting (Dr. Mario: 0 low, 1 med, 2 hi).
ColumnsOptions
interface ColumnsOptions
Options for columnsArcade.
difficulty?: FallerDifficulty— Difficulty. Arcade: DIP Normal (35 jewels a level), full speed table. Normal: DIP Easy (40), speed capped at level 9. Easy: DIP Easiest (50), capped at level 5.seed?: number— Seed.startLevel?: number— Start level: 0, 5 (+20,000) or 10 (+50,000) as the arcade offers; any other level starts with no bonus.jewelsPerLevel?: number— Jewels per level (overrides the difficulty's DIP setting).colors?: number— Jewel colours. Default 6.length?: number— Column length. Default 3.match?: number— Match length. Default 3.diagonals?: boolean— Diagonal matches. Default true.magic?: boolean— Magic jewel. Default true.cols?: number— Well width. Default 6.rows?: number— Visible well height. Default 13.
CounterBatch
interface CounterBatch
Counter gems waiting to fall on this board, with the pattern of the board that sent them.
count: number— How many.rows: string[]— Pattern rows (top to bottom).short: boolean— Some were offset: these start at the shorter countdown.
CrashDamage
interface CrashDamage
How breaks turn into counter gems. Every value is a counter-gem value before the send rate.
normal: number— Per normal gem broken.crash: number— Per crash gem broken.powerCell: number— Per cell of a power gem broken.counter: number— Per counter gem destroyed next to a break.chain: (step: number) => number— Multiplier for chain step n (1 = the first break).diamond: number— Multiplier on the diamond's own break (the chain it sets off is not reduced).
CrashGemEvent
type CrashGemEvent = | { type: 'ready' } | { type: 'spawn'; serial: number } | { type: 'move'; dx: number } | { type: 'rotate'; dir: number } | { type: 'land' } | { type: 'lock'; cells: XY[] } | { type: 'power'; gem: PowerGem } | { type: 'crash'; chain: number; cells: XY[]; gems: number; power: number; counters: number; diamond: boolean; attack: number; points: number } | { type: 'diamond'; color: number; cleared: number } | { type: 'tech' } | { type: 'counterTurn'; cells: XY[] } | { type: 'counters'; count: number } | { type: 'moveEnd'; chains: number; attack: number; gems: number } | { type: 'gameOver'; reason: string }
Events reported by CrashGemGame.drain().
CrashGemPhase
type CrashGemPhase = 'piece' | 'fall' | 'flash' | 'counter' | 'gameOver'
Resolution sub-phase.
CrashGemPreview
interface CrashGemPreview
What a placement would do, from CrashGemGame.preview.
ok: boolean— The placement fits.attack: number— Counter gems it would send.chains: number— Longest chain.gems: number— Gems it would break.over: boolean— It would end the round.field: (Cell | null)[]— The resulting board (row-major from the top of the buffer).
CrashGemRules
interface CrashGemRules
A complete crash-gem ruleset. Build one with puzzleFighter and override any field.
name: string— Name, for reports.cols: number— Board width.rows: number— Visible board height.hiddenRows: number— Rows above the board that hold gems (the spawn rows).spawnColumn: number— Column the pair appears in (its axis gem; the original: the fourth column). Blocking it ends the round.colors: number— Gem colours (2..4 with the pattern letters; more colours need patterns that use them).crashChance: number— Chance that a dealt gem is a crash gem.diamondEvery: number— A diamond replaces one gem of every Nth pair (0 = never).dealLength: number— Pairs generated before the deal repeats.speed: number— Drop speed 1..5 (the original's VS handicap: five gem drop rates).powerMin: number— Smallest power gem (width and height).counterTimer: number— Counter gem countdown.offsetTimer: number— Countdown of counter gems that still fall after some were offset.remainder: 'random' | 'left'— Columns that take a partial row of counter gems: chosen at random or filled from the left.pattern: DropPattern— This board's own drop pattern (what it sends).damage: CrashDamage— Damage model.timing: CrashGemTiming— Timings.sequence?: readonly PieceSpec[]— An authored drop order (puzzles, tests), dealt in order and repeated; replaces the generated deal.seed: number— Seed for the deal (shared by both boards for an identical drop order) and the board's own randomness.
CrashGemSnapshot
interface CrashGemSnapshot
A whole crash-gem board state: the FallerGame snapshot plus the resolution state.
faller: FallerSnapshot— The pair engine.state: unknown— Resolution state (plain data).
CrashGemTiming
interface CrashGemTiming
Frame timings of a crash-gem board (60 Hz frames).
gravity: readonly number[]— Frames per row of the falling pair at drop speeds 1..5 (index 0 = speed 1).softDrop: number— Soft drop in rows per frame.das: number— Delayed auto shift and auto repeat rate.arr: number— Auto repeat rate.lockDelay: number— Frames a landed pair waits before locking (soft drop locks at once).are: number— Entry delay before the next pair.ready: number— READY pause.flash: number— Frames broken gems flash before they go.fall: number— Frames per row when gems fall after a break.counterFall: number— Frames per row for falling counter gems.
DealMemory
interface DealMemory
A dealer's memory (carried by snapshots).
bag: string[]— Remaining bag (7-bag).last: string | null— The last kind dealt (NES reroll).index: number— Position in a sequence or loop.table: PieceSpec[]— The generated loop table (Dr. Mario).
DealRule
type DealRule = | { kind: 'bag'; pieces: readonly string[] } | { kind: 'nes'; pieces: readonly string[] } | { kind: 'uniform'; pieces: readonly string[] } | { kind: 'colors'; piece: string; blocks: number; palette: readonly number[] } | { kind: 'loop'; piece: string; blocks: number; palette: readonly number[]; length: number } | { kind: 'sequence'; items: readonly PieceSpec[]; then?: DealRule }
How pieces are dealt.
DrMarioOptions
interface DrMarioOptions
Options for drMario.
difficulty?: FallerDifficulty— Difficulty. Arcade: up to 49 speed-ups a level. Normal: up to 20. Easy: up to 10.seed?: number— Seed.level?: number— Virus level 0..20 (and beyond, at 84 viruses). Default 0.speed?: 0 | 1 | 2— Speed: 0 LOW, 1 MED, 2 HI. Default 1.virusCount?: (level: number) => number— Viruses for a level (default 4 x (level + 1), at most 84).colors?: number— Colours. Default 3.match?: number— Match length. Default 4.cols?: number— Bottle width. Default 8.rows?: number— Bottle height. Default 16.
DropPattern
interface DropPattern
A counter-gem drop pattern: the colours the attacker's counter gems take, as the opponent sees them (rows top to bottom; letters R G B Y; the pattern repeats upward and across). Row k of a drop (k = 0 at the bottom) takes the pattern's k-th row from the bottom, so a small attack always uses the bottom row.
name: string— Display name.rows: readonly string[]— Rows, top to bottom. Each row is a string of colour letters.sendRate?: (raw: number) => number— Counter gems actually sent forrawcounter gems (send-rate handicap). Default: all of them.source?: string— Where the pattern comes from.
FallerAction
type FallerAction = 'left' | 'right' | 'softDrop' | 'hardDrop' | 'rotateCW' | 'rotateCCW' | 'rotate180' | 'hold'
The input actions.
FallerApi
type FallerApi = typeof kit
game.faller — every function and type this pack carries, as one namespace.
FallerDifficulty
type FallerDifficulty = 'easy' | 'normal' | 'arcade'
Easy / Normal / Arcade. Arcade is the original tables; Normal keeps the original handling and the original early speeds but caps the top speed at a human-playable level; Easy caps it lower (per preset, documented).
FallerEvent
type FallerEvent = | { type: 'ready' } | { type: 'spawn'; kind: string; serial: number } | { type: 'move'; dx: number } | { type: 'rotate'; dir: number; kick: number } | { type: 'land' } | { type: 'hold'; kind: string } | { type: 'lock'; serial: number; cells: XY[]; tspin: TSpin } | { type: 'lines'; lines: number; rows: number[]; tspin: TSpin; backToBack: boolean; combo: number; perfectClear: boolean; points: number; attack: number } | { type: 'chain'; chain: number; cells: XY[]; cleared: number; colors: number; viruses: number; magic: boolean; points: number; attack: number } | { type: 'moveEnd'; chains: number; points: number; attack: number; viruses: number } | { type: 'score'; points: number; reason: 'softDrop' | 'hardDrop' | 'pushDown' | 'lines' | 'chain' } | { type: 'level'; level: number } | { type: 'speed'; speedIndex: number } | { type: 'allClear' } | { type: 'magic'; color: number; cleared: number } | { type: 'nuisance'; count: number } | { type: 'garbage'; rows: number } | { type: 'stageClear'; level: number } | { type: 'complete' } | { type: 'gameOver'; reason: string }
Events reported by drain(), in order.
FallerInput
interface FallerInput
One frame of player input. Held states; edges are derived from the previous frame unless pressed is given.
left?: boolean— Shift left held.right?: boolean— Shift right held.softDrop?: boolean— Soft drop held.hardDrop?: boolean— Hard drop held (acts on the press).rotateCW?: boolean— Rotate clockwise held (acts on the press).rotateCCW?: boolean— Rotate counter-clockwise held (acts on the press).rotate180?: boolean— Rotate 180 degrees held (acts on the press; only when the ruleset allows it).hold?: boolean— Hold held (acts on the press).pressed?: Partial<Record<FallerAction, boolean>>— Explicit press edges for this frame (for presses shorter than a frame). Merged with derived edges.
FallerPhase
type FallerPhase = 'ready' | 'are' | 'active' | 'clear' | 'fall' | 'stageClear' | 'gameOver' | 'complete'
The phase a game is in.
FallerRules
interface FallerRules
A complete ruleset. Build one with a preset (tetrisGuideline, tetrisNes, columnsArcade, drMario, puyoTsu) and override any field.
name: string— Name, for reports.cols: number— Well width.rows: number— Visible well height.hiddenRows: number— Buffer rows above the well.rotation: RotationSystem— Pieces and rotation.deal: DealRule— Randomiser.previews: number— Pieces shown ahead.hold: boolean— Hold slot available.hardDrop: boolean— Hard drop available.rotate180: boolean— 180-degree rotation input available.initialActions: boolean— Initial rotation/hold: a rotate or hold pressed during the entry delay applies as the piece appears.match: MatchRule— Matching rule.matchMinY?: number— Rows above this one never take part in matches (Puyo's hidden 13th row). Default: every row matches.vanishAboveY?: number— Cells that come to rest above this row vanish (Puyo's 14th row).cascade: 'rows' | 'cells' | 'linked'— Gravity after clears: 'rows' collapses cleared rows (Tetris), 'cells' drops each cell, 'linked' keeps capsule halves joined.timing: FallerTiming— Timings.scoring: ScoringFn— Scoring rule.attack?: (e: LinesEvent) => number— Attack sent for a rows clear (versus); omitted = 0.progression: ProgressionRule— Level progression.startLevel: number— Starting level.startScore?: number— Starting score (Columns start-level bonus).speed?: number— Speed setting passed to scoring (Dr. Mario 0/1/2).startSpeedIndex?: number— Starting speed index (Dr. Mario: LOW 15, MED 25, HI 31).topOut: TopOutRule— Top-out rules.viruses?: VirusRule— Viruses (Dr. Mario).magic?: MagicRule— Magic jewel (Columns).nuisance?: NuisanceRule— Nuisance (Puyo).objective: Objective— Victory condition.scoreCap?: number— Score cap (NES: 999999).layout?: readonly string[]— Authored starting stack: rows of characters, bottom-aligned; '.' empty, digits are colours, 'n' nuisance, 'g' garbage, and letters map throughlayoutColors.layoutColors?: Record<string, number>— Letter-to-colour map forlayout(e.g. { R: 0, G: 1, B: 2, Y: 3, P: 4 }).seed: number— Seed.
FallerSnapshot
interface FallerSnapshot
The whole mutable state of a game: plain data, so a snapshot is a deep copy.
frame: number— Frame counter.phase: FallerPhase— Phase.timer: number— Frames left in the phase.timerTotal: number— Phase length.cells: (Cell | null)[]— Well cells.nextId: number— Next cell id.rng: RngState— Where the well's stream stands (viruses, nuisance columns, garbage holes): a kernelRngsnapshot.dealRng: RngState— Where the piece deal's stream stands (kept separate so two games on one seed get the same pieces).dealer: DealMemory— Dealer memory.queue: PieceSpec[]— Upcoming pieces.held: PieceSpec | null— Held piece.holdUsed: boolean— Hold used since the last lock.active: Active | null— Falling piece.serial: number— Piece serial counter.acc: number— Gravity accumulator.lockTimer: number— Lock timer.lockResets: number— Lock resets used.lowestY: number— Lowest row the piece has reached.landed: boolean— The piece has touched the stack at least once.lastRotate: boolean— The last successful action was a rotation.lastKick: number— Kick test index of the last rotation.rotMemory: RotationMemory— Rotation memory (Puyo quick turn).prev: Record<FallerAction, boolean>— Held inputs last frame.dasDir: number— Auto-repeat direction.dasCounter: number— Auto-repeat counter.arrCounter: number— ARR counter.downArmed: boolean— NES soft drop armed by a fresh press.downCounter: number— NES soft-drop counter.pushDown: number— NES push-down rows.bufferedRotate: number— Buffered initial rotation (0 none).bufferedHold: boolean— Buffered initial hold.score: number— Score.level: number— Level.lines: number— Lines cleared (rows games).cleared: number— Cells cleared (matching games: jewels, puyos).pieces: number— Pieces locked.dealtThisLevel: number— Pieces dealt into play this level (Dr. Mario speed-ups).speedIndex: number— Speed index.speedUps: number— Speed-ups this level.combo: number— Combo counter (-1 = none).backToBack: boolean— Back-to-back armed.chain: number— Current chain step in this move.bestChain: number— Best chain this game.movePoints: number— Points this move.moveAttack: number— Attack this move.moveViruses: number— Viruses cleared this move.lastLines: number— Lines cleared by the last piece.lockRow: number— Row of the last lock.clearing: { cells: XY[]; rows: number[]; magic: boolean } | null— Cells being cleared.falling: [number, number][] | null— Cells falling: id -> rows.pendingNuisance: number— Nuisance waiting to fall.nuisanceCarry: number— Fractional nuisance carried between chains.allClearBonus: boolean— The next chain carries the all-clear bonus.nuisanceDropped: boolean— Nuisance has fallen this turn.pendingGarbage: { rows: number; hole: number }[]— Garbage waiting (rows games).viruses: number— Viruses left.virusesStart: number— Viruses at the start of the level.stats: Record<string, number>— Pieces dealt by kind (NES statistics).magicCooldown: number— Pieces before a magic column may appear again.targetPoints: number— Target points (Puyo; a versus layer can lower it for margin time).reason: string— Why the game ended.goalProgress: number— Objective progress: best value toward the goal.events: FallerEvent[]— Pending events.
FallerTiming
interface FallerTiming
All the frame timings of a ruleset. Values are 60 Hz frames.
gravity: (ctx: TimingContext) => number— Gravity in rows per frame for the current state (1/48 = one row every 48 frames; 20 = instant).softDrop: SoftDropTiming— Soft drop.shift: ShiftTiming— Auto-repeat.lock: LockTiming— Locking.are: number | ((ctx: TimingContext) => number)— Entry delay after a lock (or after the line clear) before the next piece appears.lineClear: number | ((ctx: TimingContext) => number)— Line-clear delay (rows games).matchFlash: number— Frames a matched set flashes before it is removed (matching games).fall: number— Frames per row of cascade gravity after a clear (and for split pieces).nuisanceFall: number— Frames per row for falling nuisance.spawnDrop: boolean— Guideline: a new piece drops one row as soon as it appears, if it can.ready: number— Frames of the READY pause before the first piece.stageClear: number— Frames of the stage-clear pause (Dr. Mario).speedUp?: SpeedUpRule— Dr. Mario speed-ups: the speed index rises everyeverypieces dealt, at mostmaxtimes per level.
FitsFn
type FitsFn = (cells: readonly XY[]) => boolean
Collision callback: true when every given cell is free and inside the well.
GemType
type GemType = 'normal' | 'crash' | 'diamond' | 'counter'
What a cell on a crash-gem board is.
GuidelineOptions
interface GuidelineOptions
Options for tetrisGuideline.
difficulty?: FallerDifficulty— Difficulty. Arcade: the full curve to 20G. Normal: gravity capped at the level-12 speed. Easy: capped at level 8, 1 s lock delay.seed?: number— Seed.startLevel?: number— Start level, 1..15. Default 1.goal?: number— Lines to finish (Marathon: 150). 0 = endless. Default 150.previews?: number— Next-queue length. Default 5.hold?: boolean— Hold. Default true.hardDrop?: boolean— Hard drop. Default true.rotate180?: boolean— Allow a 180-degree rotate input (not in SRS proper). Default false.das?: number— Delayed auto shift in frames. Default 10 (the Guideline's recommended 167 ms).arr?: number— Auto repeat rate in frames. Default 2 (33 ms).are?: number— Entry delay in frames. Default 6 (the Guideline's recommended ARE).lineClear?: number— Line-clear delay in frames. The Guideline leaves it to each game; default 24 (est.).lockDelay?: number— Lock delay in frames. Default 30 (0.5 s).lockResets?: number— Move resets before a forced lock. Default 15.perfectClear?: boolean— Score perfect clears (later Guideline games). Default false.cols?: number— Well width. Default 10.rows?: number— Visible well height. Default 20.
GuidelineScoringOptions
interface GuidelineScoringOptions
Options for guidelineScoring.
perfectClear?: boolean— Add the perfect-clear bonus used by some Guideline games (800/1200/1800/2000, 3200 for a B2B tetris). Default false.
HardDropEvent
interface HardDropEvent
Rows moved by a hard drop.
type: 'hardDrop'— Event kind.rows: number— Rows moved.level: number— Current level.
LineMatch
interface LineMatch
Line-of-N matching result: groups by colour plus the Columns "lines of three" count.
groups: MatchGroup[]— One group per colour, every cell that is part of a qualifying run.linesOfN: number— Sum over every maximal run of (length - (n - 1)): a run of 4 counts as two lines of three.
LinesEvent
interface LinesEvent
A piece locked in a rows game (Tetris), with or without lines.
type: 'lines'— Event kind.lines: number— Lines cleared by this piece.tspin: TSpin— T-spin classification of the lock.difficult: boolean— True when this clear is "difficult" (four or more lines, or a T-spin that cleared lines).backToBack: boolean— True when the previous line clear was also difficult (back-to-back applies).combo: number— Consecutive line-clearing pieces minus one (0 for the first, -1 when this piece cleared nothing).perfectClear: boolean— True when the clear left the well empty.levelBefore: number— Level before this clear.levelAfter: number— Level after this clear's lines are counted.
Link
type Link = 'left' | 'right' | 'up' | 'down'
Which neighbour a capsule half is joined to (Dr. Mario), or undefined for a free cell.
LockTiming
interface LockTiming
Locking rule.
mode: 'move' | 'step' | 'gravity'— 'move': lock delay that moves and rotations reset, up toresetstimes (restored on reaching a new lowest row; Guideline extended placement). 'step': lock delay reset only by falling a row. 'gravity': lock as soon as a gravity or soft-drop step is blocked (NES Tetris, Dr. Mario).delay: number— Frames a grounded piece waits before locking.resets: number— Maximum resets: 'move' mode counts moves and rotations on the ground; 'step' mode counts floor kicks that lift a landed piece (which would otherwise allow endless stalling). 0 = unlimited in 'step' mode.softDropLocks?: boolean— Holding soft drop on a grounded piece locks it at once (Puyo, Columns).
MagicRule
interface MagicRule
Columns' magic jewel.
dangerRows?: number— Offer a magic column when the stack has reached within this many rows of the top.every?: number— Also offer one every N pieces (0 = never).cooldown: number— Pieces before another can be offered.minLevel?: number— Lowest level at which it can appear.
MatchGroup
interface MatchGroup
A matched set of cells found after a lock.
cells: XY[]— Cells in the group.color: number— Their colour (-1 for full rows).
MatchRule
type MatchRule = | { kind: 'rows' } | { kind: 'lines'; length: number; diagonals: boolean } | { kind: 'groups'; size: number }
What counts as a match after a lock.
NesOptions
interface NesOptions
Options for tetrisNes.
difficulty?: FallerDifficulty— Difficulty. Arcade: the full table to the 1-frame level 29. Normal: never faster than level 18 (3 frames a row). Easy: never faster than level 9 (6).seed?: number— Seed.startLevel?: number— Start level 0..19 (the NES menu offers 0-9, and 10-19 with A held). Default 0.cols?: number— Well width. Default 10.rows?: number— Visible well height. Default 20.preview?: boolean— Show the next piece (the NES SELECT toggle). Default true.
NuisanceRule
interface NuisanceRule
Puyo nuisance rules (used by the versus layer).
maxPerDrop: number— Most nuisance that falls after one piece (Tsu: 30, five rows of six).targetPoints: number— Score per nuisance sent (Tsu: 70).allClearBonus: number— Extra nuisance sent on the chain after an all clear (Tsu: 30).
Objective
type Objective = | { kind: 'endless' } | { kind: 'lines'; lines: number } | { kind: 'chain'; chain: number; pieces?: number } | { kind: 'allClear'; pieces?: number } | { kind: 'clearColor'; color: number; pieces?: number } | { kind: 'score'; score: number; pieces?: number }
A goal that ends the game in victory.
PieceBlock
interface PieceBlock
One block of a piece, relative to the piece origin.
dx: number— Column offset from the piece origin.dy: number— Row offset from the piece origin (y down).color: number— Colour index.link?: Link— The partner this block is joined to, for pieces whose halves stay joined after locking (capsules).
PiecePose
interface PiecePose
A piece's position and orientation.
rot: number— Rotation state index.x: number— Origin column.y: number— Origin row.colors: number[]— Block colours in order (cycling systems change these instead of the shape).
PieceSpec
interface PieceSpec
What the randomiser deals: a piece kind and its colours (one per block for coloured pieces).
kind: string— The piece kind, e.g. 'T', 'pair', 'capsule', 'column'.colors: number[]— Colours, one per block (or one shared colour for tetrominoes).magic?: boolean— Columns' magic jewel column: clears every jewel of the colour it lands on.
PlacementPreview
interface PlacementPreview
What placing a piece would do, from FallerGame.preview.
ok: boolean— The placement fits.points: number— Points it would score (drops, clears, chains).chains: number— Longest chain it would set off.attack: number— Attack it would send (nuisance or garbage lines).lines: number— Lines it would clear (rows games).over: boolean— It would end the game.field: (Cell | null)[]— The resulting well cells (row-major from the top of the buffer).
PowerGem
interface PowerGem
A power gem: a fused rectangle of one colour.
id: number— Power gem id (stable while it exists; changes when it grows).color: number— Colour.x: number— Left column.y: number— Top row.w: number— Width in cells.h: number— Height in cells.
ProgressionRule
type ProgressionRule = | { kind: 'lines'; perLevel: number; base: number; maxLevel?: number; nesFirst?: boolean } | { kind: 'cleared'; perLevel: number; maxLevel?: number } | { kind: 'pieces'; perLevel: number; maxLevel?: number } | { kind: 'viruses'; maxLevel?: number } | { kind: 'none' }
How the level advances.
PushDownEvent
interface PushDownEvent
NES push-down points: rows of the final continuous soft drop, added when the piece locks.
type: 'pushDown'— Event kind.rows: number— Rows moved.level: number— Current level.
PuyoMission
interface PuyoMission
A Puyo mission (a "nazo puyo" style puzzle): a starting stack, the pairs to play, and a goal.
name: string— Short name.text: string— The goal, shown to the player.layout: readonly string[]— Starting stack, bottom-aligned rows of R G B Y P (and n for nuisance).pairs: readonly string[]— The pairs, as two letters each: axis then child (e.g. 'RG').goal: Objective— The objective (with a piece limit).
PuyoOptions
interface PuyoOptions
Options for puyoTsu.
difficulty?: FallerDifficulty— Difficulty. Arcade: Tsu Normal's four colours, full speed curve. Normal: four colours, speed capped at level 8. Easy: three colours (Tsu Easy), capped at level 4.seed?: number— Seed.colors?: number— Colours dealt (3 Easy, 4 Normal, 5 Hard in Tsu).popSize?: number— Group size that pops. Default 4.piecesPerLevel?: number— Endless: pieces per speed level. Default 30.mission?: number | PuyoMission— A mission instead of endless play (index intoPUYO_MISSIONS, or your own).cols?: number— Well width. Default 6.rows?: number— Visible well height. Default 12.
PuzzleFighterOptions
interface PuzzleFighterOptions
Options for puzzleFighter.
difficulty?: 'easy' | 'normal' | 'arcade'— Easy / Normal / Arcade. Arcade: the given drop speed. Normal: one slower (min 1). Easy: speed 1 and longer lock delay.seed?: number— Seed.pattern?: string | DropPattern— Drop pattern key inDROP_PATTERNS, or your own. Default 'bands'.speed?: number— Drop speed 1..5. Default 3.diamond?: number— Diamond damage factor: 0.5 original, 0.8 HD Remix.cols?: number— Board width. Default 6.rows?: number— Board height. Default 12.colors?: number— Colours. Default 4.crashChance?: number— Crash gem chance per gem. Default 0.125 (estimate).diamondEvery?: number— Diamond every N pairs (0 = off). Default 25.powerMin?: number— Smallest power gem side. Default 2.
RotationMemory
interface RotationMemory
Per-piece rotation state the engine keeps between rotations (Puyo's quick-turn flag).
quickTurn: boolean— Set by a blocked rotation of a wedged vertical pair; the next press turns it 180 degrees.
RotationResult
interface RotationResult extends PiecePose
The result of a successful rotation. kick is the index of the kick test used (0 = no offset).
kick: number— Index of the kick test that succeeded (0 = the plain rotation).
RotationSystem
interface RotationSystem
A rotation system: shapes, spawn rule and rotation resolution for a family of pieces.
readonly id: string— Identifier, e.g. 'srs', 'nrs', 'cycle', 'pair', 'capsule'.readonly kinds: readonly string[]— The piece kinds this system defines, in their canonical order.states(kind: string): number— Number of rotation states for a kind.blocks(kind: string, pose: PiecePose): PieceBlock[]— The blocks of a piece at a pose.spawn(spec: PieceSpec, cols: number): PiecePose— Where a piece spawns in a wellcolswide.rotate(kind: string, pose: PiecePose, dir: 1 | -1 | 2, fits: FitsFn, memory: RotationMemory): RotationResult | null— Resolve a rotation:dir1 = clockwise, -1 = counter-clockwise, 2 = 180 degrees. Null when it fails.spinCentre?(kind: string, pose: PiecePose): XY | null— The centre of a piece's bounding box for the 3-corner T-spin test, or null if the kind has none.
ScoringEvent
type ScoringEvent = SoftDropEvent | HardDropEvent | PushDownEvent | LinesEvent | ChainEvent
Everything a scoring function is asked to price.
ScoringFn
type ScoringFn = (event: ScoringEvent) => number
A scoring rule: points for an event (0 for events it does not score).
ShiftTiming
interface ShiftTiming
Horizontal auto-repeat. modern = DAS then ARR; nes = the NES counter (shift at 16, reload 10, charged by a wall).
style: 'modern' | 'nes'— Style of auto-repeat.das: number— Frames before auto-repeat starts (NES: 16).arr: number— Frames between repeats (0 = instantly to the wall; NES: 6).chargeInDelay: boolean— Keep charging DAS while no piece is in play (ARE, line clear). NES freezes it.blockedBySoftDrop?: boolean— NES: left/right do nothing while soft drop is held.
SoftDropEvent
interface SoftDropEvent
Rows moved by a soft drop (Guideline, Columns, Puyo: scored as they happen).
type: 'softDrop'— Event kind.rows: number— Rows moved.level: number— Current level.
SoftDropTiming
type SoftDropTiming = | { kind: 'factor'; factor: number; min?: number } | { kind: 'rate'; rowsPerFrame: number } | { kind: 'nes'; first: number; repeat: number }
Soft drop speed.
SpeedUpRule
interface SpeedUpRule
Speed-ups within a level (Dr. Mario).
every: number— Pieces dealt between speed-ups.max: number— Most speed-ups per level.
TableRotationOptions
interface TableRotationOptions
Options for a table-driven rotation system (tableRotation).
id: string— Identifier reported byRotationSystem.id.shapes: Record<string, XY[][]>— Shapes per kind, one list of cells per rotation state, in clockwise order.colors?: Record<string, number>— Colour per kind (tetrominoes are one colour). Defaults to the kind's index.spawnRot?: Record<string, number>— Spawn rotation per kind. Default 0.spawnMode: 'box' | 'pivot'— Spawn origin. 'box' centres the bounding box (rounded left) with its top atspawnY(SRS); 'pivot' places the pivot at column floor(cols / 2), rowspawnY(NES).spawnY: number— Spawn row of the origin.kicks?: (kind: string, from: number, to: number) => readonly (readonly [number, number])[]— Kick tests for a rotation, in y-DOWN cell offsets, tried in order. Default: only [0, 0].spinCentres?: Record<string, XY>— Bounding-box centre per kind for the 3-corner T-spin test.
TimingContext
interface TimingContext
Context passed to timing functions.
level: number— Current level.speedIndex: number— Current speed index (Dr. Mario speed-ups; otherwise 0).pieces: number— Pieces locked so far.frame: number— Frame counter.lockRow: number— Row of the last lock (piece origin, 0 = top visible row).rows: number— Visible rows in the well.lines: number— Lines cleared by the last piece (rows games).
TopOutRule
interface TopOutRule
Top-out rules.
blockOut?: boolean— Game over when a new piece overlaps the stack (Tetris, Dr. Mario, Columns).lockOut?: boolean— Game over when a piece locks entirely above the visible well (Guideline).deathCells?: readonly XY[]— Game over when any of these cells is filled after a move resolves (Puyo: column 3 of the top row).overflow?: boolean— Game over when anything is left above the visible well after a move resolves (Columns).
TSpin
type TSpin = 'none' | 'mini' | 'full'
How a T-spin was classified at lock (Guideline 3-corner rule with the pointing-side mini test).
VirusRule
interface VirusRule
Dr. Mario virus placement.
count?: (level: number) => number— Viruses for a level. Default min(84, 4 x (level + 1)).maxRow?: (level: number) => number— Highest row (1 = bottom) a virus may occupy at a level (NES table: 10, then 11/12/13 from level 15).palette: readonly number[]— Virus colours.spacing?: boolean— The NES distance-two colour rule (no virus shares a colour with the cells two away in each direction).
XY
interface XY
A grid coordinate: x is the column (0 = left), y is the row (0 = top visible row; hidden rows are negative).
x: number— Column, 0 = left.y: number— Row, 0 = the top visible row; rows above the well are negative.


