Appearance
Timers
timers is a generic recurring trigger mechanic. It owns periodic scheduling; the action that happens on a fire stays in your game.
ts
import { timers, type TimersData } from "@idlekitjs/mechanics/timers";Mental model
Timers are gameplay mechanics, not the core loop scheduler. The core SimulationLoop decides when simulation ticks happen. timers runs inside that simulation and answers "has this gameplay timer fired yet?"
Use timers for small recurring game actions: drain a container, refresh a shop, advance a patrol, spawn a wave.
Minimal example
ts
import { timers, type TimersData } from "@idlekitjs/mechanics/timers";
interface State {
timers: TimersData;
coins: number;
}
const timerExt = timers<State>({
definitions: [
{ id: "drain-bin", every: 5 },
{ id: "patrol", every: 10, autoStart: false },
],
getData: (state) => state.timers,
setData: (state, timers) => {
state.timers = timers;
},
onFire: (id, state, fires) => {
if (id === "drain-bin") {
state.coins += fires * 10;
}
},
});
engine.use(timerExt);Definitions and state
ts
interface TimerDef {
id: string;
every: number;
autoStart?: boolean;
metadata?: Record<string, unknown>;
}
interface TimerState {
remaining: number;
running: boolean;
}every is seconds between fires and must be finite and greater than zero. autoStart defaults to true.
Runtime state is a Record<string, TimerState> in your game state. Missing entries are materialized from definitions on the first update.
API
ts
timerExt.start(state, "patrol");
timerExt.stop(state, "patrol");
timerExt.trigger(state, "patrol");
timerExt.isRunning(state, "patrol");
timerExt.progressFraction(state, "patrol");| Method | Behavior |
|---|---|
start | Resume a stopped timer. Progress is kept, not reset. |
stop | Pause a timer and freeze its progress. |
trigger | Fire once manually, running or not, and reset the period. |
isRunning | Read the running flag. |
progressFraction | Progress toward the next firing in [0, 1]. |
Unknown ids return false for mutating methods and 0 or false for readers.
Offline fires
When a large dt covers several periods, the mechanic calls onFire(id, state, fires) once with fires > 1.
ts
onFire: (id, state, fires) => {
if (id === "income") {
state.coins += fires * 10;
}
};Handle the count in one pass. Timers do not own the action they trigger.