I always assumed an incremental/idle game — the "tap the rock, buy miners, watch huge numbers roll" genre — needed some kind of engine underneath. It needs the opposite. There is no world, no physics, no sprite sheet. The entire thing is a handful of variables, one requestAnimationFrame loop, and a geometric price curve. I built a full idle mine — tap to mine ore by hand, buy generators that dig for you every second, prestige for a permanent multiplier — in about 120 lines of vanilla JavaScript that saves to localStorage. Here is how it actually fits together.
The whole game state is a few numbers
There is nothing to draw and nothing to simulate. The complete state is: how much ore you hold now, how much you've earned this run (that value alone decides your prestige reward), how many of each generator you own, your click level, and your permanent gems. Everything on screen is just a formatted view of those variables, which is why the game serialises to one tiny string.
The tick loop earns over real time
A clicker has to keep paying you when you're not clicking, so the heart of it is a delta-time loop. Every frame I measure dt — the real seconds since the last frame — and add production × dt:
function loop(now){
const dt = (now - last) / 1000; // real seconds elapsed
last = now;
const gained = perSec() * dt; // rate x time = ore
ore += gained; earnedThisRun += gained;
render();
requestAnimationFrame(loop);
}
Multiplying a per-second rate by elapsed seconds makes income frame-rate independent: at 60fps or 30fps you earn the same per real second. That one idea is the entire secret behind "it earns while idle" — you're not clicking, but the clock keeps ticking. Total production is just a sum, Σ rate × count, over every generator type.
Exponential cost scaling is the whole genre
If a generator cost the same forever, you'd buy infinite copies the instant you could afford one and the game would be over. So the golden rule: each copy costs 15% more than the last. The price of the n-th unit is baseCost × 1.15ⁿ:
const GROWTH = 1.15;
function genCost(i){ return GENS[i].baseCost * Math.pow(GROWTH, count[i]); }
That geometric curve snowballs far faster than your linear income, which is what forces you to stop spamming the cheapest miner and unlock bigger ones. The demo's UNDERSTAND tab even exposes the ROI tool players use without naming it: payback time = cost ÷ extra-per-second. A drill costing 1,200 that adds 8/s pays back in 150s; if a cheaper generator pays back faster, buy that first. That's the same capital-budgeting math businesses use, hiding inside a game.
Prestige — burn it down for a permanent bonus
Eventually the cost curve outruns even your compounding income and progress crawls. Prestige is the escape valve: reset the whole run — ore, generators, upgrades all to zero — in exchange for permanent gems. Gems are a square root of what you earned, so they arrive as big diminishing milestones, and each adds +2% to all future production forever:
function pendingGems(){ return Math.floor(Math.sqrt(earnedThisRun / 2500)); }
function prestigeMult(){ return 1 + gems * 0.02; } // +2% per gem, permanent
You come back weaker on paper but carrying a multiplier that makes the next climb much faster. Knowing when to prestige is the deepest decision in the genre.
Two details that make it feel real
Numbers race past a billion in minutes, so 1400000000 gets formatted by dividing by a thousand and counting suffixes — K, M, B, T — but only at draw time, so full precision survives underneath. And persistence is what makes an idle game idle: I autosave state plus a timestamp every few seconds, and on load I compute how many seconds passed while you were gone and pay out perSec() × offlineSeconds (capped). Offline time is literally just one enormous dt handed to the same loop. No engine required — the economy is the game.
Play it (tap the rock or hit Space; it earns while the tab's closed):
https://dev48v.infy.uk/game/day44-idle-clicker.html
Top comments (0)