The pack
mergeWorkshop
function mergeWorkshop(): Pack<{ mergeWorkshop: MergeWorkshopApi; }>
Install renderer-independent merge rules and cancellable worker jobs.
Helpers
createMergeWorkshop
function createMergeWorkshop(config: MergeConfig)
Builds a merge workshop from a level config. Throws if the config is invalid.
generateMergeWorkshop
function generateMergeWorkshop(options: MergeGenerateOptions = {}): MergeLevel
Generate the actual order forest and finite resources, then prove a route through the public rules.
playMergeStrategy
function playMergeStrategy(config:MergeConfig,strategy:MergeStrategy,maxStates=2000):MergeStrategyResult
A deterministic player with no lookahead, search or access to the solution certificate. All three reserve exact order items; they differ in which supply they select. Terminal space failures are proved separately, never inferred from a search timeout.
Classes
MergeWorkshop
class MergeWorkshop
The rules of a merge-and-deliver puzzle: merge matching items up a tier, fill orders from the board and storage, and draw new items from producers with energy and cooldowns. Moves are undoable. Build one with createMergeWorkshop; it throws if the config is invalid.
readonly config: MergeConfig— The level as given (a copy).constructor(config: MergeConfig)get state()— A copy of the current state.get solved()— Whether every order has been fulfilled.isOpen(index: number, s = this.history.state)— Whether cell numberindexexists and is open: enough orders are complete for itsunlockAfter. Checks the current state unless one is given.transition(state: MergeState, action: MergeAction): { valid: true; state: MergeState; } | { valid: false; reason: string; }— Pure atomic transition; rejection never consumes inventory, energy or history.inspect(a: MergeAction)— Reports whether an action would be accepted now, as{ valid, reason }, without playing it.reasonis empty when valid.apply(a: MergeAction)— Plays an action and records it for undo. Returns{ valid: true, reason: '' }, or{ valid: false, reason }with the state untouched.undo()— Takes back the last action. Returns false when there is nothing to undo.redo()— Replays the action just undone. Returns false when there is nothing to redo.reset()— Returns to the opening position and forgets the actions played.restore(state: MergeState)— Makestatethe new opening position, with no moves behind it:resetreturns here. A state that fails validation changes nothing.resourceFailure(s = this.history.state): string | null— Necessary resource proof: reserve lower exact orders before carrying pairs upward.requiredMerges(s = this.history.state): number | null— Exact for a mass-balanced finite supply: every item is required and every merge reduces item count by one.actions(s = this.history.state): MergeAction[]— Symmetry reduction is exact: free cells have unrestricted drag access; sealed items are distinct by type.solve(maxStates = 20000): MergeSolution— Searches for a route that completes every order from the current state, visiting at mostmaxStatesdistinct states (a whole number from 1 to 1,000,000; default 20000) and routes up to 512 actions long. Throws ifmaxStatesis out of range. It does not change the game.get lost()— Cheap proof only. For full deadlock detection call solve(), which distinguishes budget exhaustion.
Constants
mergeLayouts
const mergeLayouts: readonly ["rectangle", "courtyard", "wings", "terraces", "islands", "mosaic"]
The board shapes the generator can lay out, as names for MergeGenerateOptions.layout.
mergeStrategies
const mergeStrategies: readonly ["queue-first", "match-first", "order-first"]
The built-in no-lookahead play styles playMergeStrategy can follow: queue-first draws from producers first, match-first prefers items that match one already on the board, order-first prefers items an available order needs.
Types
MergeAction
type MergeAction = { type: 'merge' | 'move'; from: number; to: number; } | { type: 'produce'; producer: number; to: number; } | { type: 'fulfil'; order: string; } | { type: 'store'; from: number; } | { type: 'retrieve'; from: number; to: number; } | { type: 'wait'; ticks: number; }
One thing the player can do. Every action is atomic: it either happens in full or is rejected and changes nothing.
merge: combine the item in cellfrominto the matching item in cellto, which goes up a tier.move: move the item in cellfromto the empty cellto.produce: take the next item from producer numberproducer(an index intoproducers) into the empty cellto.fulfil: deliver the order with this id.store: put the item in cellfrominto storage.retrieve: bring the item at storage indexfromout into the empty cellto.wait: lettickspass (1 to 10000), for producers to cool down.
MergeCell
interface MergeCell
One place on the board where an item can sit.
id: string— Unique name for the cell.x: number— Column on the board's grid, from 0 to 19.y: number— Row on the board's grid, from 0 to 19.unlockAfter?: number— Number of orders that must be complete before the cell opens; default 0, open from the start. A cell that is closed at the start holds no item.
MergeConfig
interface MergeConfig
A complete level: the chains, the board, the starting items, the orders, the producers and the energy and storage allowances. new MergeWorkshop throws on a config it cannot play.
id: string— Name of the level; not empty.chains: { id: string; maxTier: number; }[]— The item chains, 1 to 8, each with a unique id and the highest tier it reaches (1 to 10).cells: MergeCell[]— The board's cells, 2 to 100 with unique ids and unique positions. Items are addressed by index into this list.items: (MergeItem | null)[]— The item starting in each cell, in the order ofcells, or null for an empty cell: exactly one entry per cell.orders: MergeOrder[]— The orders to fulfil, 1 to 24. The level is complete when all are fulfilled.producers: MergeProducer[]— The producers, 0 to 8.storage: number— Storage slots for items taken off the board, 0 to 16.energy: number— Energy at the start, 0 to 1,000,000; each production spends its producer'senergyCost.
MergeGenerateOptions
interface MergeGenerateOptions
Controls for generateMergeWorkshop. Each defaults from difficulty, so a bare call is a complete level; set one to change that part of the puzzle and keep the rest.
seed?: number— Seed for the whole level, a whole number from 0 to 4294967295; default 1. The same seed and options give the same level.difficulty?: 'easy' | 'medium' | 'hard' | 'very-hard'— Sets the defaults for every other control:easy,medium,hardorvery-hard; defaultmedium. Harder levels have more chains, orders and merges, less working space and plan-ahead supply queues.layout?: MergeLayout | 'auto'— The board shape: one ofmergeLayouts, orauto(default), which picks one by seed. Throws on any other name.chains?: number— Item chains in play, 1 to 6. Default 2 to 5 by difficulty (easy 2 or 3).orders?: number— Orders to fill, 1 to 12. Default 2 to 9 by difficulty.merges?: number— Merges the solution needs, exactly, 1 to 80. Default about 3, 11, 23 or 39 by difficulty, plus a little variation.workspace?: number— Empty open cells at the start, 1 to 12; fewer is harder. Default 4, 3, 2 or 1 by difficulty (easy adds up to 2).storage?: number— Storage slots, 0 to 8. Default 3 on easy, otherwise 1.sealed?: number— Starting items to seal, 0 to 12; default 0. A sealed item cannot be moved, stored or delivered until a matching item is merged onto it. The generator seals fewer if no matching pair is available.producers?: number— Producers with supply queues, 0 to 6. Default 0 on easy, otherwise 3. With none, every item starts on the board.challenge?: boolean— Whether to accept only levels where each of themergeStrategiesends in a space dead end, so the player has to plan. Default true except on easy.
MergeItem
interface MergeItem
One piece on the board, in storage or in a producer's queue: an item of a chain at a tier.
chain: string— Id of the chain it belongs to, fromMergeConfig.chains.tier: number— Its level in the chain, a whole number from 0 to the chain'smaxTier; two items of the same chain and tier merge into one a tier higher.sealed?: boolean— Whether it is fixed: it cannot be moved, stored or delivered, but a matching item can still be merged onto it, which frees the result. Default false.
MergeLevel
interface MergeLevel
A generated level: a playable config, a solution proved by the rules, and what was asked for and measured.
seed: number— The seed it was generated from.difficulty: NonNullable<MergeGenerateOptions['difficulty']>— The difficulty it was generated for.layout: MergeLayout— The board shape used, resolved fromautoif that was asked for.config: MergeConfig— The playable level, forcreateMergeWorkshop.solution: MergeAction[]— A sequence of actions that completes every order when played from the start, found and replayed by the generator.parameters: Required<MergeGenerateOptions>— Every control as used, defaults filled in;layoutis as requested, so it may still beauto.metrics: { requiredMerges: number; solutionActions: number; chains: number; orders: number; sealed: number; supplies: number; initialFree: number; unlockCells: number; searchStates: number; structure: string; challenge: boolean; strategies: { strategy: MergeStrategyResult['strategy']; outcome: MergeStrategyResult['outcome']; }[]; }— Figures measured on the level:requiredMergesandsolutionActionsof the solution, the counts ofchains,orders,sealeditems,suppliesheld in producer queues,initialFreecells andunlockCellslocked at the start,searchStatesthe solver visited, a layout-freestructuresignature for comparing levels, whetherchallengewas applied, and theoutcomeof eachstrategiesplay.
MergeOrder
interface MergeOrder
Something to deliver: a set of items that are used up when it is fulfilled.
id: string— Unique name for the order.items: { chain: string; tier: number; count: number; }[]— The items it consumes: for each entry,countitems of the chain at exactly thattier, taken from the board first and then from storage. Sealed items do not count. 1 to 16 entries, each count 1 to 64.requires?: string[]— Ids of orders that must be complete before this one can be fulfilled; the dependencies may not form a cycle.reward?: number— Credits added toMergeState.creditswhen it is fulfilled, a whole number from 0 to 1,000,000; default 0.
MergeProducer
interface MergeProducer
A source of items that hands them out one at a time, in order, for energy.
id: string— Unique name for the producer.queue: MergeItem[]— The items it will produce, in order: up to 128, none sealed.requires?: string[]— Ids of orders that must be complete before it can produce.cooldown?: number— Ticks that must pass after one production before it can produce again; a whole number from 0 to 1000, default 0.energyCost?: number— Energy each production costs, a whole number from 0 to 100; default 1.
MergeSolution
interface MergeSolution
The result of solve.
status: 'solved' | 'unsolvable' | 'budget-exhausted'—solvedif a route was found,unsolvableif the search proved there is none, orbudget-exhaustedif it hit the state or depth limit first.actions: MergeAction[]— The actions of the route when solved; empty otherwise.visited: number— How many distinct states the search visited.reason: string— Why:validated-routewhen solved,search-limitwhen the budget ran out, otherwise the failing resource such asinsufficient-gem-tier-2orno-reachable-solution.requiredMerges: number | null— The exact number of merges any solution needs, or null when the supply does not exactly match the orders.
MergeState
interface MergeState
Everything that changes during play. Plain data; save it and give it back to restore.
items: (MergeItem | null)[]— The item in each cell, in the order ofMergeConfig.cells, or null when empty.storage: MergeItem[]— The items in storage, oldest first.produced: number[]— For each producer, how many items of its queue it has already produced.ready: number[]— For each producer, the tick from which it can next produce.completed: string[]— Ids of the orders fulfilled so far, in the order they were completed.energy: number— Energy left.credits: number— Credits earned from order rewards so far.tick: number— Time in ticks: each accepted action adds one andwaitadds several. Cooldowns count against it.merges: number— Merges made so far.productions: number— Items produced so far.deliveries: number— Orders fulfilled so far.rearrangements: number— Moves, stores and retrievals so far.
MergeStrategy
type MergeStrategy=typeof mergeStrategies[number]
One of mergeStrategies.
MergeStrategyResult
interface MergeStrategyResult
How one strategy fared on a level.
strategy:MergeStrategy— The strategy that was played.outcome:'solved'|'resource-dead-end'|'space-dead-end'|'unresolved'—solvedif it completed every order;resource-dead-endif the items left cannot fill the remaining orders;space-dead-endif the supply suffices but a complete search shows no way on from where it stopped;unresolvedif neither could be shown.actions:MergeAction[]— The actions it played, in order.state:MergeState— The state where it stopped.
MergeWorkshopApi
type MergeWorkshopApi = typeof kit & { generateMergeWorkshopAsync(options?: kit.MergeGenerateOptions, job?: { signal?: AbortSignal; }): Promise<kit.MergeLevel>; solveMergeWorkshopAsync(config: kit.MergeConfig, state: kit.MergeState, maxStates?: number, job?: { signal?: AbortSignal; }): Promise<kit.MergeSolution>; }
Public independent rules and helpers exposed by the composed pack.


