Phaser AE

Tray matching puzzles

Pro packv3.0.0-3Grid

The rules of triple-match tray puzzles: pick an uncovered tile or object, it drops into a small tray, and three of a kind clear. Layered tile boards and scattered 3D piles play by the same rules, and a seeded generator makes levels whose difficulty is screened rather than guessed.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 19, 2026

Tray matching puzzles: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

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 — greedyWins over greedyTrials, 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 to capacity. 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' (only blockers).
  • blockers?: { item: string; by: string; }[] — Extra rules that item cannot be selected until by has 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 times matchSize, at most 120. Default set by difficulty and layout.
  • kinds?: number — Distinct matching keys, 2 up to groups. Default set by difficulty and layout, capped at groups.
  • 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, from matchSize plus 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 to capacity minus 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 against maxGreedyWinRate, 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 with levelIndex), a tile or pile family name, 'stacks', or 'custom' (implied by masks or surfaceMask).
  • 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 with surfaceMask. Default null.
  • surfaceMask?: TrayLayerMask | null — One authored top layer, with the lower layers, keys and route generated; not combined with masks. 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 as width; generated tile levels use cell units.
  • y: number — Position down the board, within plus or minus 1000, in the same units as depth.
  • 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): X is 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 from config, 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 with surfaceMask.
  • surfaceMask?: TrayLayerMask | null — One authored top layer, with the lower layers generated beneath it; not combined with masks.

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 against maxMoves.
  • reshuffles: number — Reshuffles applied so far.

TrayTileLayout

type TrayTileLayout = typeof trayTileLayouts[number]

One of the tile silhouette names in trayTileLayouts.