Skip to content

Offline progress

Credits the time the player was away — after a reload (loaded event) and after returning from a hidden tab (resume event) — by advancing the simulation, capped by your policy.

ts
import { offlineProgress } from "@idlekitjs/plugins/offline-progress";

Usage

ts
engine.use(offlineProgress({ maxMs: 8 * 60 * 60 * 1000 })); // cap: 8 h

// Later:
await engine.load(save); // emits "loaded" -> catch-up runs automatically
engine.start();

That's the whole integration. On loaded, the plugin computes Date.now() - savedAt from the save's timestamp; on resume (emitted by the page-lifecycle bridge) it receives the elapsed background time. Either way it clamps to [0, maxMs] and calls engine.advance(elapsed / 1000).

Why advance, not a lump sum

engine.advance replays the away time in the same fixed steps as real time (see Simulation loop). Idle mechanics are non-linear — caps, thresholds, produce→spend loops — and a single giant dt would distort them. With fixed-step catch-up, offline production is exactly what attended production would have been, and every mechanic (producers' cycles, crafting jobs, boost expiry) handles it correctly with no special code.

Options

OptionDefaultRole
maxMs24 hCap on credited time, in milliseconds

Guards: a missing/invalid maxMs falls back to the default; negative elapsed times (clock skew, corrupted savedAt) are ignored entirely.

Choosing the cap

The cap is game design, not engineering: it decides whether sleeping is a strategy. Common choices are 2–24 h. Communicate it in-game ("You were away 3 h — production credited") so the cap feels like a feature, not a theft.

Interaction with page-lifecycle

The plugin and the page-lifecycle bridge are the two halves of away-time handling:

  • Reload / cold start → the save's savedAt powers the catch-up (loaded).
  • Same-session background → page-lifecycle pauses the loop, then emits resume with the hidden duration.

Install both, or reloads work but backgrounding is lost (on desktop, a hidden tab's rAF is throttled, not stopped — without page-lifecycle you'd get slow, inconsistent background simulation instead of a clean pause + credit).

Limits

  • No offline summary data — it advances the state but doesn't report what happened. Snapshot before/after around engine.load if you want a "while you were away" dialog:
ts
const before = engine.state.coins;
await engine.load(save);
const gained = engine.state.coins - before;
  • The cap is global — per-mechanic offline policies (e.g. "crafting continues but boosts freeze") are a game-side composition.