The pack
conveyorSort
function conveyorSort(): Pack<{ conveyorSort: typeof kit; }>
Compose the conveyor sort rules, generators and solving player under game.conveyorSort.
Helpers
CONVEYOR_PALETTE
const CONVEYOR_PALETTE: readonly string[]
Twelve distinct cube colours, CSS hex strings.
conveyorHint
function conveyorHint(game: ConveyorSort): ConveyorHint | null
The next shooter worth sending, or null when none is (wait for the riders on the belt, or the level is stuck). Prefers a shooter that spends all its shots, then the most hits, then the bench (to free a slot); never one that could come back to a bench without room. When nothing can hit and the belt is empty it digs: it sends the front shooter whose column has the most useful shooter behind it. A heuristic, not a solver; pure, so the state is unchanged.
conveyorPicture
function conveyorPicture(options: ConveyorPictureOptions): ConveyorPicture
Generate a seeded picture to clear. Throws on a size, colour count or style it cannot make.
conveyorPoint
function conveyorPoint(track: ConveyorTrack, pos: number, margin = 1): ConveyorPoint
Where a shooter pos units along the belt is drawn, in board cell units, with the belt margin cells outside the
board's edge (default 1, from 0 to 16). Along a side it is level with the station it is passing; round a corner it
follows a quarter circle. pos wraps at the lap length.
conveyorTrack
function conveyorTrack(width: number, height: number, options: ConveyorTrackOptions = {}): ConveyorTrack
Lay a belt round a width × height board. Stations sit one per column along the top and bottom and one per row
along the sides, each a unit apart, with cornerGap units round each corner. Throws on a size outside 1 to 64 or
an unknown option.
createConveyorSort
function createConveyorSort(config: ConveyorSortConfig): ConveyorSort
Make a conveyor sort level from a config. Throws, naming the field, on a config it cannot play.
generateConveyorSort
function generateConveyorSort(options: ConveyorGenerateOptions): ConveyorLevel
Generate a level: split each colour's hits into shooters, find an order that clears the picture by sending one
shooter at a time, deal that order into the columns, then move shooters within their columns until the solving
player needs about as much of the bench as the difficulty asks. The result comes with the winning taps, checked
by playing them. Throws when no winnable arrangement turns up in attempts tries (a bench of 0 with large shots,
say).
pictureFromRows
function pictureFromRows(rows: readonly string[], palette: Readonly<Record<string, string>>): ConveyorPicture
A picture from rows of characters, top row first: each character names a colour in palette and . or a space
is an empty cell. Rows may differ in length; the widest sets the width. Throws on a character the palette lacks or
a size outside 1 to 64.
playConveyor
function playConveyor(config: ConveyorSortConfig, options: { maxLaunches?: number } = {}): ConveyorPlay
Play a level to the end with conveyorHint, sending one shooter (or link group) at a time and running the belt
until it is empty before the next. Stops after maxLaunches (default 2000). A win proves the level winnable; a
loss proves only that this simple player lost.
replayConveyor
function replayConveyor(config: ConveyorSortConfig, launches: string[]): ConveyorPlay
Replay recorded launches, each when the belt is empty, and report how the level ended.
stationLine
function stationLine(track: ConveyorTrack, station: ConveyorStation): number[]
The board cells a station fires along, outermost first, as indices into a row-major width × height array.
Classes
ConveyorSort
class ConveyorSort
A conveyor sort level in play: shooters from the fronts of the columns ride a belt round the picture and fire at
cubes of their colour; ones with shots left wait on a bench, and a return to a full bench loses. Time moves only
through advance or tick, in whole fixed steps, so the same launches at the same steps replay exactly at any
frame rate. Get one from createConveyorSort.
constructor(config: ConveyorSortConfig)get config(): ConveyorSortConfig— A copy of the config this level was made from.get state(): ConveyorState— A copy of the state: everything a save needs.get track(): ConveyorTrack— The belt's layout.get status(): ConveyorState['status']—'playing','won'or'lost'.get time(): number— Seconds of play so far: whole steps run divided by the step rate.get remaining(): number— Cubes still on the board.get progress(): number— Fraction of the picture's hits landed, 0 to 1.get freeTrays(): number— Trays free at the gate: the trays less the shooters on the belt or waiting to get on.get freeBench(): number— Free bench slots.get busy(): number— Shooters on the belt or waiting at the gate to get on.riders(): { id: string; pos: number; color: string; ammo: number }[]— The shooters on the belt, first on first, with their colour and shots left: what a presenter draws each frame.colorOf(id: string): string | undefined— A shooter's colour, or undefined for an unknown id.ammoOf(id: string): number— A shooter's shots left, or 0 for an unknown id.cellAt(x: number, y: number): { color: string; hits: number } | null— The colour of the cube on a cell, or null when the cell is empty or off the board.pointOf(id: string, margin = 1): ConveyorPoint | null— Where a shooter on the belt is drawn, in board cell units with the beltmargincells out; null if it is not on the belt.pointAt(pos: number, margin = 1): ConveyorPoint— Where the beltposstations from the gate is drawn; seeconveyorPoint.available(): ConveyorChoice[]— Every shooter that can be tapped now (each column's front and each bench shooter), with whether it can go.refusal(id: string, pick = false): ConveyorRefusal | null— Whylaunch(id)(orpick(id)whenpickis true) would be refused now, or null if it would be accepted.launch(id: string): ConveyorResult— Send a shooter: the front of a column (with its whole link group) or one from the bench. It takes a tray and gets on the belt as soon as there is room at the gate.pick(id: string): ConveyorResult— Booster: send any queued shooter, wherever it stands in its column, with its link group.addBench(count = 1): ConveyorResult— Booster: add bench slots (1 to 8, default 1).addTrays(count = 1): ConveyorResult— Booster: add trays at the gate (1 to 8, default 1).shuffle(): ConveyorResult— Booster: shuffle the queued shooters that have no link among the places they stand, from the level's seed. Column lengths and linked shooters stay where they are.continueRun(slots = 1): ConveyorResult— After a loss: add bench slots (1 to 8, default 1), put the shooter held at the gate on the first one and play on. The continue a game sells.advance(seconds: number): ConveyorEvent[]— Run the simulationsecondson (0 to 10 per call), in whole steps; time left over is kept for the next call. Returns what happened, in order.tick(): ConveyorEvent[]— Run one fixed step: the next waiting shooter gets on if there is room, then every rider moves and fires.lap(id: string): ConveyorLap | undefined— What one lap of shooteridalone would do on the board as it is now, ignoring the riders already on the belt. Pure; the state is unchanged. Undefined for an unknown id.laps(ids: string[]): ConveyorLap[]— Laps of several shooters sent one after another, each alone and each on the board the last one left; what a linked group would do, near enough. Pure. Throws on an unknown id.exposed(): Record<string, number>— Cubes a shooter of each colour could hit right now from a firing station, each cube counted once.restore(state: ConveyorState): void— Put back a statestategave, of this level. Throws on one that does not fit the level.
Types
ConveyorCell
interface ConveyorCell
One cube of the picture.
x: number— Column, from 0 at the left.y: number— Row, from 0 at the top.color: string— Colour, a CSS colour string: only shooters of exactly this string hit it.hits?: number— Hits it takes to clear, 1 to 999; default 1.
ConveyorChoice
interface ConveyorChoice
A shooter that could be sent now, from available().
id: string— The shooter tapped.group: string[]— Every shooter that would go with it (its link group), itself included.from: 'column' | 'bench'— Where it is: the front of a column or a bench slot.index: number— The column or bench slot index.ok: boolean— Whetherlaunchwould accept it now.reason?: ConveyorRefusal— Why not, whenokis false.
ConveyorCorner
type ConveyorCorner = 'bottom-left' | 'bottom-right' | 'top-right' | 'top-left'
A corner of the board: where the gate can stand.
ConveyorDifficulty
type ConveyorDifficulty = 'easy' | 'medium' | 'hard' | 'expert'
How hard a generated level is, graded by how much of the bench the solving player needs.
ConveyorEvent
type ConveyorEvent = | { type: 'launched'; id: string; from: 'column' | 'bench' | 'pick'; index: number } | { type: 'entered'; id: string } | { type: 'shot'; id: string; x: number; y: number; station: number; hitsLeft: number; cleared: boolean; ammo: number } | { type: 'spent'; id: string; pos: number } | { type: 'returned'; id: string; slot: number } | { type: 'lost'; id: string; reason: 'bench-full' } | { type: 'continued'; id: string; slot: number } | { type: 'won' }
What happened, in order. Positions are board cells.
ConveyorGenerateOptions
interface ConveyorGenerateOptions
What to generate. Only seed is required.
seed: number— Seed, a whole number from 0 to 4294967295. The same options give the same level.picture?: ConveyorPicture— The picture to clear; default one fromconveyorPicturewith the size, colours and style below.width?: number— Generated picture width, 4 to 64; default 20.height?: number— Generated picture height, 4 to 64; default 20.colors?: number— Generated picture colours, 2 to 12; default 4.style?: ConveyorPictureStyle— Generated picture style; default'sprite'.columns?: number— Columns of shooters, 1 to 12; default 4.ammo?: number[]— The shot counts shooters are made with, each 1 to 999; default[10, 20, 30, 40]. A colour's last shooter takes what is left.difficulty?: ConveyorDifficulty— Difficulty; default'medium'.links?: number— Chance, 0 to 1, that two shooters of one colour sent one after the other in the solution are linked; default 0.rules?: ConveyorRuleOptions— The rules: trays, bench, speed, line of fire, burst, track and so on.attempts?: number— Arrangements to try for the difficulty, 1 to 400; default 60.
ConveyorHint
interface ConveyorHint
A suggested launch, from conveyorHint.
id: string— The shooter to tap.hits: number— Shots its lap (its link group's laps) would land on the board as it is now.why: 'clears' | 'hits' | 'digs'—'clears': it would spend every shot.'hits': it would land some and come back.'digs': nothing can land a shot now, so it goes round empty to bring the shooter behind it to the front.
ConveyorLap
interface ConveyorLap
What one lap of a shooter alone would do on the board as it is now, from lap().
hits: number— Shots it would land.spent: boolean— Whether it would run out of shots and leave before the gate.cells: { x: number; y: number }[]— The cells it would hit, in order (a tough cube can appear more than once).
ConveyorLevel
interface ConveyorLevel
A generated level and the proof it can be won.
config: ConveyorSortConfig— The level, ready forcreateConveyorSort.solution: string[]— A winning sequence of taps, each made when the belt is empty;replayConveyorchecks it.benchPeak: number— Most of the bench the solution needs at once.difficulty: ConveyorDifficulty— The difficulty asked for.
ConveyorLineOfFire
type ConveyorLineOfFire = 'first' | 'first-match'
How a shooter picks its target in the line in front of a station.
ConveyorPicture
interface ConveyorPicture
A picture of cubes: its size and its cubes.
width: number— Width in cells.height: number— Height in cells.cells: ConveyorCell[]— The cubes, one per filled cell.
ConveyorPictureOptions
interface ConveyorPictureOptions
What to generate.
seed: number— Seed, a whole number from 0 to 4294967295; the same options give the same picture.width: number— Width in cells, 4 to 64.height: number— Height in cells, 4 to 64.colors?: number— Colours used, 2 to 12; default 4.style?: ConveyorPictureStyle— Kind of picture; default'sprite'.palette?: readonly string[]— CSS colours to draw from, at leastcolorsof them; defaultCONVEYOR_PALETTE.fill?: boolean— Sprite only: fill the cells round the figure with a background colour (the picture is then a full rectangle); default true.
ConveyorPictureStyle
type ConveyorPictureStyle = 'sprite' | 'blobs' | 'rings' | 'stripes'
Kinds of generated picture: 'sprite' a mirrored figure with an outline, 'blobs' patches of colour,
'rings' concentric bands, 'stripes' straight bands.
ConveyorPlay
interface ConveyorPlay
How a played level ended, from playConveyor or replayConveyor.
won: boolean— Whether every cube was cleared.launches: string[]— The shooters tapped, in order, each when the belt was empty.benchPeak: number— Most shooters on the bench at once.seconds: number— Seconds of play the run took.
ConveyorPoint
interface ConveyorPoint
A point on the belt in board cell units (the board spans x 0..width, y 0..height, y down), facing its travel.
x: number— Across, in cells from the board's left edge; negative or pastwidthbeside the board.y: number— Down, in cells from the board's top edge; negative or pastheightbeside the board.angle: number— Direction of travel in radians, 0 along +x, clockwise positive on screen.side: ConveyorSide | null— The side being passed, or null on a corner.
ConveyorRefusal
type ConveyorRefusal = | 'not-playing' | 'unknown-shooter' | 'not-at-front' | 'link-not-at-front' | 'not-queued' | 'no-tray' | 'not-lost' | 'bad-count'
Why an action was refused.
ConveyorResult
type ConveyorResult = { valid: true; reason?: undefined; events: ConveyorEvent[] } | { valid: false; reason: ConveyorRefusal; events: [] }
The answer to an action: done with its events (and no reason), or refused with a reason and no change.
ConveyorRider
interface ConveyorRider
A shooter on the belt.
id: string— The shooter's id.pos: number— Distance travelled from the gate this lap, in stations.next: number— Index in the track's stations of the next one it will pass.
ConveyorRuleOptions
type ConveyorRuleOptions = Omit<ConveyorSortConfig, 'width' | 'height' | 'cells' | 'columns'>
The rules a generated level plays by: everything in a config but the picture and the columns.
ConveyorShooter
interface ConveyorShooter
A shooter waiting in a column.
id: string— Unique id, 1 to 100 characters; events and actions name it.color: string— Colour, a CSS colour string: it hits only cubes of exactly this string.ammo: number— Shots it carries, 1 to 9999. It leaves for good when they run out.link?: string— Shooters with the same link name go on the belt together and only when every one of them is at the front of its column. The link holds while they queue; on the bench they are separate.
ConveyorSide
type ConveyorSide = 'bottom' | 'right' | 'top' | 'left'
A side of the board that a conveyor runs along.
ConveyorSortConfig
interface ConveyorSortConfig
A conveyor sort level: the picture, the columns of shooters and the rules.
width: number— Board width in cells, 1 to 64.height: number— Board height in cells, 1 to 64.cells: ConveyorCell[]— The picture: cubes on distinct cells inside the board, 1 to 4096. Empty cells are allowed anywhere.columns: ConveyorShooter[][]— The shooters, one array per column, front first: 1 to 12 columns. Only a column's front shooter can be sent. Each colour's ammo must be at least the hits its cubes take, so no level is lost for want of shots.trays?: number— Trays at the gate: how many shooters can be on the belt or waiting to get on at once, 1 to 20; default 5.bench?: number— Bench slots for shooters that finish a lap with shots left, 0 to 16; default 5. A return to a full bench loses.speed?: number— Belt speed in stations per second, 1 to 200; default 24.spacing?: number— Least gap between shooters getting on, in stations, 0.25 to 20; default 1.5.tickRate?: number— Simulation steps per second, 30 to 960; default 120. Rules move only in whole steps.lineOfFire?: ConveyorLineOfFire—'first'(default): a shooter hits the outermost cube in the line only if it is its colour, so the picture peels from the outside.'first-match': it hits the nearest cube of its colour, shooting past the others.burst?: number— Shots a shooter fires at one station as it passes, 1 to 8; default 1.track?: ConveyorTrackOptions— The belt's layout: gate corner, direction, firing sides and corner length.seed?: number— Seed for the shuffle booster, a whole number from 0 to 4294967295; default 1.
ConveyorState
interface ConveyorState
Everything that changes in play; a save holds exactly this.
hits: number[]— Hits left on each cell, row-major (y * width + x); 0 is an empty cell.columns: string[][]— Shooter ids still queued, one array per column, front first.links: Record<string, string>— Link names of queued shooters that still have one.ammo: Record<string, number>— Shots left, by shooter id.staging: string[]— Shooters sent and holding a tray, waiting for room to get on the belt, first first.belt: ConveyorRider[]— Shooters on the belt, in the order they got on.bench: (string | null)[]— The bench, one entry per slot: a shooter id or null for a free slot.trays: number— Trays at the gate now (boosters add more).status: 'playing' | 'won' | 'lost'—'playing','won'(every cube cleared) or'lost'(a shooter came back to a full bench).held: string | null— The shooter stopped at the gate when the level was lost; null otherwise.tick: number— Simulation steps run.carry: number— Seconds given toadvanceand not yet run as a whole step.shots: number— Shots fired.launches: number— Times a shooter was sent, from a column or the bench.benchPeak: number— Most shooters on the bench at once so far.rng: number[]— The shuffle booster's random stream.
ConveyorStation
interface ConveyorStation
A place on the belt level with one row or column of the board, where a passing shooter fires inward once.
side: ConveyorSide— The side of the board it is on.index: number— The column (top and bottom sides) or row (left and right sides) it fires along, from 0.at: number— Its distance along the belt from the gate, in station units.fires: boolean— False on a side that does not fire.
ConveyorTrack
interface ConveyorTrack
The belt's layout: the order of its sides and stations and its length. Get one from conveyorTrack.
width: number— Board width in cells.height: number— Board height in cells.length: number— One lap's length in station units: two widths, two heights and four corners.gap: number— Station units round each corner.sides: ConveyorSide[]— The sides in the order a shooter passes them.stations: ConveyorStation[]— Every station in the order a shooter passes it, with nondecreasingat.corners: { corner: ConveyorCorner; at: number }[]— The corners in the order a shooter reaches them, the gate first at 0, with their distance along the belt.
ConveyorTrackOptions
interface ConveyorTrackOptions
How the conveyor is laid round the board. Every field is optional.
gate?: ConveyorCorner— The corner where shooters get on and off; default'bottom-left'.direction?: 'clockwise' | 'counterclockwise'— Which way the belt runs as seen on screen (y down); default'counterclockwise', which from a bottom-left gate goes along the bottom to the right first, then up the right side, along the top and down the left side.firing?: ConveyorSide[]— The sides whose stations fire; the belt still carries shooters past the others. Default all four.cornerGap?: number— Track length round each corner, in station units, 0 to 8; default 1.


