The pack
trayMatch
function trayMatch(): Pack<{ trayMatch: typeof kit }>
Install renderer-independent puzzle rules and level generation.
Helpers
analyzeTrayMatch
function analyzeTrayMatch(config: TrayConfig, options: TrayAnalysisOptions = {}): TrayAnalysis
Evaluate an owned configuration using three visible-board strategies and seeded tie breaks. Never mutates the level.
createTrayMatch
function createTrayMatch(config: TrayConfig, solution: TrayAction[] = []): TrayMatch
Construct a pure puzzle, optionally using a replay-checked certificate for cheap hints.
generateTrayMasks
function generateTrayMasks(options: TrayMaskOptions = {}): TrayLayoutBlueprint
Compose filled/empty layer maps into small staggered tiles; no canvas or renderer is required.
generateTrayMatch
function generateTrayMatch(options: TrayGenerateOptions = {}): TrayLevel
Generate a deterministic, fully playable layered or spatial pile level at runtime. All solving and layout policies live here.
traySurfaceCount
function traySurfaceCount(shape: TrayTileLayout): number
Natural exposed count for a motif; the existing five boards retain their 22-tile surface.
Classes
TrayMatch
class TrayMatch
Headless tray matching with accessibility adapters, target quotas, atomic moves and complete history.
constructor(config: TrayConfig, solution: TrayAction[] = [])get state(): TrayState— Owned snapshot, safe to save or inspect.get items(): TrayItem[]— Live items including keys assigned by reshuffle; returned as owned copies.get capacity(): number— Maximum held objects before matches clear.get solved(): boolean— All objects matched, or configured quotas satisfied.get lost(): boolean— Full unresolved tray, exhausted moves, or inaccessible remaining items. Undo/reset remain available.blockedBy(id: string): string[]— Exact accessibility reasons, independent of camera visibility.isAccessible(id: string): boolean— Whether an item can be selected now, before tray/move-limit checks.legalActions(): TrayAction[]— All legal selections.preview(action: TrayAction): ActionResult<TrayEvent>— Validate without changing state, history or certificate.apply(action: TrayAction): ActionResult<TrayEvent>— Commit one legal selection and immediately resolve any match.solve(maxStates = 20000): SearchResult<TrayAction>— Proven continuation: replay a compatible certificate, otherwise perform bounded search. Never mutates play.reshuffle(seed: number): ActionResult<TrayEvent>— Reassign only remaining keys while preserving counts, held items and geometry; validate a new continuation. Undoable and deterministic.undo(): boolean— Restore the previous complete state, including any reshuffle.redo(): boolean— Replay an undone state.reset(): void— Restore original keys, level and empty tray.
Constants
trayPileLayouts
const trayPileLayouts: readonly ["heap", "two-heaps", "crescent", "scatter"]
Pile footprints suited to scattered objects rather than layered picture tiles.
trayTileLayouts
const trayTileLayouts: readonly ["x", "cross", "hourglass", "bands", "twin-peaks", "t", "i", "l", "e", "diamond", "ring", "heart", "star", "flower", "stairs", "procedural"]
Tile silhouettes. Letter boards are inspired by Triple Tile; the additional motifs are our designs.
Types
TrayAction
interface TrayAction
Select one accessible item.
id: string— Id of the item to select.
TrayAnalysis
interface TrayAnalysis
Strategy screening, not a predicted human win rate or proof of minimum capacity.
greedyWins: number— Trials won, summed over all three strategies.greedyTrials: number— Trials run in all: trials per strategy times three.greedyWinRate: number—greedyWinsovergreedyTrials, 0 to 1.meanClearedFraction: number— Mean share of the items matched and cleared per trial, 0 to 1, whether or not the trial won.initialTriples: number— Complete matching groups the items accessible at the start could already make, counted across all keys.dependencyEdges: number— Blocking relationships at the start: an item blocked by two others counts twice.policies: { name: 'match-first' | 'group-first' | 'unlock-first'; wins: number; trials: number }[]— Wins and trials for each of the three strategies.
TrayAnalysisOptions
interface TrayAnalysisOptions
Reproducible matching-policy trials, independent of the level generator's RNG.
seed?: number— Unsigned 32-bit seed for the strategies' tie breaks; default 0x735a2.trialsPerPolicy?: number— Trials run for each of the three strategies, 1 to 64; default 8.
TrayConfig
interface TrayConfig
Layered footprints cover lower tiles; piles use overlapping 3D bounds; explicit blockers work in either.
items: TrayItem[]— The objects on the board, 1 to 120, with unique ids.capacity?: number— Most items the tray holds, 2 to 30; a selection into a full tray is refused. Default 7.matchSize?: number— Identical keys that clear together, 2 up tocapacity. Default 3.mode?: 'layered' | 'pile' | 'explicit'— How blocking is decided:'layered'(a higher overlapping footprint covers a tile; default),'pile'(overlapping 3D bounds) or'explicit'(onlyblockers).blockers?: { item: string; by: string; }[]— Extra rules thatitemcannot be selected untilbyhas been removed; allowed in every mode.targets?: Record<string, number>— Quotas of cleared groups by key, each 1 to 120; the puzzle is won as soon as all are met. Default none, so every item must clear.maxMoves?: number— Most selections allowed, 1 to 10000; reshuffles do not count. Default unlimited.
TrayDifficulty
type TrayDifficulty = 'easy' | 'medium' | 'hard' | 'very-hard'
Available runtime difficulty presets, from relaxed matching to sustained planning.
TrayEvent
type TrayEvent = { type: 'selected'; id: string; index: number; } | { type: 'matched'; key: string; ids: string[]; } | { type: 'reshuffled'; seed: number; ids: string[]; }
Ordered events to animate after atomic rule application.
TrayGenerateOptions
interface TrayGenerateOptions
Runtime level controls; interlock and strategy screening control planning difficulty.
seed?: number— Unsigned 32-bit integer, 0 allowed. Default 1.difficulty?: TrayDifficulty— Preset:'easy','medium','hard'or'very-hard'. Default'medium'.mode?: 'layered' | 'pile'—'layered'for tile boards or'pile'for spatial piles. Default'layered'.groups?: number— Matching sets on the board, 2 to 40; items are groups timesmatchSize, at most 120. Default set by difficulty and layout.kinds?: number— Distinct matching keys, 2 up togroups. Default set by difficulty and layout, capped atgroups.layers?: number— Layers of stacking: 1 to 3 for shaped boards and scattered piles, 1 to 8 for'stacks'. Default set by difficulty.columns?: number— Columns of a'stacks'layout, 2 to 16; shaped boards and scattered piles ignore it. Default set by difficulty.capacity?: number— Tray capacity, frommatchSizeplus 1 to 16. Default 7.matchSize?: number— Identical items that clear together, 2 to 5. Default 3.pressure?: number— Planned peak tray fill for the winning route, 1 tocapacityminus 1. Default 2, 3, 5 or 6 by difficulty.interlock?: number— 0 to 1: how strongly lower tiles are covered by several upper ones, coupling the order matches become available. Default 0, 0, 0.7 or 1 by difficulty.candidateBudget?: number— Most seeded candidates screened againstmaxGreedyWinRate, 1 to 96. Default 1, 1, 32 or 64 by difficulty.maxGreedyWinRate?: number— Ceiling, 0 to 1, on the share of screening trials the three bot strategies may win. Default 1, 1, 0.25 or 1/24 by difficulty.layout?: TrayLayout— Board family:'auto'(default, cycling withlevelIndex), a tile or pile family name,'stacks', or'custom'(implied bymasksorsurfaceMask).exposed?: number— Initially accessible surface tiles on shaped boards, 1 to 36. Default the shape's natural count.levelIndex?: number— Level number that selects the family under'auto', 0 to 1,000,000. Default 0.layoutPeriod?: number— Levels each family lasts under'auto', 1 to 1000. Default 1.gridSize?: number— Procedural surface canvas and minimum mask size in cells, 8 or 10. Default 10.rotation?: number— Quarter turns applied to generated surfaces, 0 to 3. Default 0.mirror?: boolean— Reflect generated surfaces before they are rotated. Default false.symmetry?: 'horizontal' | 'central' | 'none'— Symmetry of a procedural surface:'horizontal','central'or'none'. Default'horizontal'.masks?: TrayLayerMask[] | null— A complete authored top-to-bottom blueprint that replaces generated geometry; not combined withsurfaceMask. Default null.surfaceMask?: TrayLayerMask | null— One authored top layer, with the lower layers, keys and route generated; not combined withmasks. Default null.spread?: number— Spacing of pile footprints, 1 to 1.5; higher values reduce overlap. Default 1.
TrayItem
interface TrayItem
A stable object ID and matching key, with optional renderer-independent 3D/footprint pose.
id: string— Unique item name, used by actions, blockers and events.key: string— Matching key: items with the same key group together in the tray and clear together.x: number— Position across the board, within plus or minus 1000, in the same units aswidth; generated tile levels use cell units.y: number— Position down the board, within plus or minus 1000, in the same units asdepth.z: number— Height of the item's centre, within plus or minus 1000; in layered mode a higher item covers lower ones that it overlaps.width?: number— Extent along x, above 0, used for overlap tests. Default 0.9.depth?: number— Extent along y, above 0, used for overlap tests. Default 0.9.height?: number— Extent along z, above 0; only pile mode uses it, to tell whether one item sits above another. Default 0.45.
TrayLayerMask
interface TrayLayerMask
Filled X / empty . cells. Offsets locate the top-left cell centre in board cell units; layers are top first.
rows: string[]— Rectangular grid rows, top to bottom, all the same length (1 to 16 rows and columns):Xis a tile,.is empty.offsetX: number— Board position of the first cell's centre across the board, in cell units; half-cell steps, within plus or minus 16.offsetY: number— Board position of the first cell's centre down the board, in cell units; half-cell steps, within plus or minus 16.
TrayLayout
type TrayLayout = 'auto' | TrayTileLayout | TrayPileLayout | 'stacks' | 'custom'
Shallow tile silhouettes, automatic level progression, or spatial stacked piles.
TrayLayoutBlueprint
interface TrayLayoutBlueprint
Renderer-independent grid blueprint, with blank matching keys ready for a certified deal.
masks: TrayLayerMask[]— The layer masks, top layer first.items: TrayItem[]— The tiles those masks make, with blank matching keys until a deal assigns them.
TrayLevel
interface TrayLevel
Proven level, certificate measurements and independent matching-strategy screening.
config: TrayConfig— The playable puzzle: items, capacity, match size and mode.solution: TrayAction[]— A winning sequence of selections fromconfig, validated by replaying it through the rules; it is not the shortest.seed: number— The seed requested, which names the whole candidate sequence.difficulty: TrayDifficulty— The difficulty preset used.parameters: Required<TrayGenerateOptions>— Every option as resolved, defaults filled in, so passing it back regenerates this level.blueprint: TrayLayoutBlueprint | null— The tile masks and items behind a shaped board, or null for piles and stacks.metrics: { items: number; layers: number; kinds: number; initialChoices: number; hidden: number; solutionPeak: number; capacity: number; layout: Exclude<TrayLayout, 'auto'>; analysis: TrayAnalysis; candidatesTested: number; difficultyTargetMet: boolean; }— Measurements of the level: item, layer and kind counts, initial choices, covered items, route peak, capacity, resolved layout, strategy screening and whether the win-rate target was met.
TrayMaskOptions
interface TrayMaskOptions
Bounded mask composition controls. Custom masks replace shape, item count and automatic layering.
seed?: number— Unsigned 32-bit seed for the procedural surface and the lower-layer staggering. Default 1.shape?: TrayTileLayout— The surface silhouette. Default'x'.items?: number— Total tiles across all layers, 1 to 120. Default 66.layers?: number— Layers of tiles, 1 to 3. Default 3.exposed?: number— Tiles on the top layer, 1 to 36 (up to the item count for an authored surface). Default the shape's natural count.interlock?: number— 0 to 1: how strongly lower tiles prefer positions covered by several upper tiles. Default 0.gridSize?: number— Procedural surface canvas and minimum mask size in cells, 8 or 10. Default 10.rotation?: number— Quarter turns applied to the generated surface, 0 to 3. Default 0.mirror?: boolean— Reflect the generated surface before it is rotated. Default false.symmetry?: 'horizontal'|'central'|'none'— Symmetry of a procedural surface:'horizontal'(default),'central'or'none'.masks?: TrayLayerMask[] | null— A complete authored blueprint of 1 to 3 layers, top first, replacing automatic geometry; not combined withsurfaceMask.surfaceMask?: TrayLayerMask | null— One authored top layer, with the lower layers generated beneath it; not combined withmasks.
TrayMatchApi
type TrayMatchApi = typeof kit
Public pure tray-match rules and runtime seeded generation API.
TrayPileLayout
type TrayPileLayout = typeof trayPileLayouts[number]
One of the pile footprint names in trayPileLayouts.
TrayState
interface TrayState
Complete serializable state including reshuffled keys and held item IDs.
items: TrayItem[]— Every item, including any keys changed by reshuffles.removed: string[]— Ids of the items taken off the board, in selection order.tray: string[]— Ids of the items held in the tray, with identical keys grouped together.cleared: Record<string, number>— Groups cleared so far, counted by key.moves: number— Selections made so far, counted againstmaxMoves.reshuffles: number— Reshuffles applied so far.
TrayTileLayout
type TrayTileLayout = typeof trayTileLayouts[number]
One of the tile silhouette names in trayTileLayouts.


