Appearance
Mechanics overview
A mechanic is a generic gameplay primitive: the machinery of an idle-game system — cost curves, drop tables, job lifecycles, effect stacking — packaged as an Extension<T> and stripped of every game-specific decision. Your game provides the data (definitions, names, balancing); the mechanic provides the behavior.
The catalog
| Mechanic | Gives you | Typical use |
|---|---|---|
| Producers | Tiered production, geometric costs, bulk buy, cascades | Generators, factories, populations |
| Modifiers | A shared registry of stat bonuses resolved per target | Upgrades, buffs, card effects |
| Collections | Seeded gacha packs, collectible levels, effect publishing | Cards, artifacts, pets |
| Projects | One-shot/repeatable purchases with triggers and persistence | Research, upgrades, story beats |
| Crafting | Timed transformation: inputs → job → duration → outputs | Kitchens, workshops, refineries |
| Boosts | Temporary stackable effects with clean expiry | Power-ups, buffs, debuffs |
| Containers | Finite-capacity holders with multiple content types | Inventories, bins, tanks |
| Pickups | Temporary identified items with lifetime and collection | Loot, bonuses, map items |
| Timers | Generic periodic gameplay triggers | Drains, refreshes, patrols |
They compose: collections and boosts publish effects into the modifier registry; producers resolve their yield and speed against it — none of them knowing the others exist.
They also have optional Economy adapters. Those adapters are official but live in @idlekitjs/mechanics/<mechanic>/economy, not in the mechanics barrel and not in @idlekitjs/economy/adapters. The core mechanics remain Economy-free.
Mechanic vs game code
The line is simple: mechanism in the package, meaning in the game.
ts
// The mechanic knows HOW producers work…
const ext = producers<State>({
definitions: TIERS, // …your game says WHAT the tiers are
getColumn: (s) => ({ owned: s.owned, total: s.total, progress: s.progress }),
setColumn: (s, patch) => {
/* write back */
},
resource: {
get: (s) => s.potatoes,
add: (s, n) => {
s.potatoes += n;
},
},
});A mechanic never contains a resource name, an icon, a price point or a balance decision. If you find yourself wanting one in a mechanic, it belongs in your game's data files.
Mechanic vs plugin
Both are extensions; the difference is what they contribute. Mechanics are rules — remove producers and your game plays differently. Plugins are practicality — remove autosave and your game plays the same but loses progress. The packaging follows the distinction: gameplay primitives here, browser bridges in @idlekitjs/browser, and engine policies in @idlekitjs/plugins.
The shared conventions
Mechanics share the same wiring style. Once you've wired one, the others use the same patterns:
- Factory in, extension out.
producers(options)returns aProducersExtension<T>— the baseExtension<T>plus public methods (purchase,start,grant, ...). Keep the reference;engine.useinstalls it but doesn't hand it back. - State through accessors. Runtime data (owned counts, jobs, active boosts) lives in your state as plain JSON, read/written through
get*/set*options. Saving needs nothing extra. - Reassign for reactivity. Structural writes go through
set*with fresh objects so dirty-key tracking sees them. High-frequency counters (cycle progress, job elapsed) are mutated in place on purpose — read them via helpers likeprogressFraction. - When a mechanic has
update(state, dt), it tolerates anydt. Large offline catch-ups complete every due cycle/job/expiry in one pass. - Load handling is mechanic-specific. Mechanics with derived data or completion indexes rebuild it on
loaded; smaller primitives with only plain state do not need a load hook. - Callbacks, not custom events. Mechanics expose callbacks such as
onPurchase,onComplete,onChangeandonExpirewhen they need to report changes. - Statuses for runtime refusals, throws for bad definitions. "Can't afford" returns a status your UI can render; a duplicate recipe id throws at startup.
- Economy is optional. Use Economy adapters when you want explicit resources and atomic transactions around a mechanic; skip them when a mechanic's native API is enough.
Adding your own
A custom mechanic is just an extension following the conventions above — the extensions guide shows the factory pattern. If it's generic enough for two games, structure it like the built-ins (module conventions) and it will feel native.