Appearance
Requirements
A requirement is a non-consuming gate on a transaction.
ts
import { allOf, not, resourceAtLeast, resourceAtMost } from "@idlekitjs/economy";ts
interface Requirement<T> {
id: string;
label?: string;
isMet(state: T, economy: EconomyReader<T>): boolean;
progress?(state: T, economy: EconomyReader<T>): { current: number; target: number };
}Gates, not costs
Requirements observe state and decide whether an action is allowed. They do not consume resources.
ts
const canUnlock = resourceAtLeast<State>("currency:science", 500);Costs are consumed when a transaction executes:
ts
const transaction = {
id: "buy:lab",
requirements: [resourceAtLeast("currency:science", 500)], // gate only
cost: [["currency:coins", 1000]], // consumed
};Do not model a gate as a zero cost. Anything that can block execute belongs in requirements.
Built-in helpers
resourceAtLeast(resourceId, amount) is met when the resource balance is at least the amount. It reports progress as { current, target }.
resourceAtMost(resourceId, amount) is met when the balance is at most the amount.
allOf(...requirements) is met when every child requirement is met.
not(requirement, label?) is met when the child requirement is not met.
ts
const requirement = allOf(
resourceAtLeast<State>("currency:science", 500),
not(resourceAtLeast<State>("upgrade:lab", 1), "Lab not already owned"),
);When a requirement fails during preview, the transaction gets a requirement-failed diagnostic. If the requirement exposes progress, that progress is copied into the failure.
Custom requirements
Custom requirements are plain objects. They receive the state and an EconomyReader<T>.
ts
const hasEmptySlot = {
id: "has-empty-slot",
isMet: (state: State) => state.usedSlots < state.maxSlots,
progress: (state: State) => ({ current: state.usedSlots, target: state.maxSlots }),
};Keep any mutation out of requirements. They run during pure preview.