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.