Snow317.io is a small 8-bit clicker RPG we're building in plain JavaScript: no framework, no engine, one page. You click ATTACK, monsters drop coins, and coins buy weapons, armour and pets. Pets deal damage for you. It's a spin-off of SnowRSPS, a custom game server we've run since 2012.
The code is easy. The economy is the hard part. Here's what we've learned so far.
1. Pick your growth curves before you write content
Every incremental game comes down to two curves:
- Income growth: how fast coins per second rise
- Cost growth: how fast prices rise
If costs grow faster than income, the player hits a wall. If income grows faster, the game is "solved" in an afternoon. The usual pattern is exponential costs and multiplicative income:
const cost = (base, owned, rate = 1.15) =>
Math.floor(base * Math.pow(rate, owned));
Tune rate per item category. Pets (passive DPS) should get steeper curves than weapons (which only help when you click), or idle play makes clicking pointless.
2. Tiny numbers need a plan
Auto Attack in Snow317.io unlocks at 0.0001 coins, so the first purchase is practically free. That's deliberate: it teaches "the game plays itself" within seconds. But tiny decimals in plain JS floats mean display bugs, like 0.30000000000000004.
What helped us:
- Keep the internal value raw, and format only at render time.
- Use one formatter for every number on screen (decimals at the start, suffixes like K/M later).
const fmt = n => n < 1 ? n.toFixed(4)
: n < 1e3 ? n.toFixed(1)
: Intl.NumberFormat('en', { notation: 'compact' }).format(n);
3. Idle needs a manual counterweight
If pets do everything, the player stops coming back. We kept a few reasons to click:
- Critical hits on manual attacks
- A Heal button, because HP matters and the monster hits back
- Waves and Cycles as visible milestones
Rule of thumb: active play should earn noticeably more per minute than idle, but idle should never earn zero.
4. Resets are part of the economy
Our reset costs you your coins and half your pets. A reset is an economic sink: it removes value so prices make sense again. If it's too soft, nothing matters. If it's too harsh, players quit. We're still tuning it, and it's the number we argue about most.
5. Save to localStorage, defensively
No accounts means localStorage is the save file:
function save(state) {
try { localStorage.setItem('s317', JSON.stringify({ v: 1, ...state })); }
catch (e) { /* quota or private mode: fail quietly */ }
}
function load() {
try {
const s = JSON.parse(localStorage.getItem('s317'));
return s && s.v === 1 ? s : null;
} catch { return null; }
}
Version your save format from day one. Your economy will change, and old saves need a migration path, not a crash.
6. Drop tables are an economy too
Each monster in Snow317.io has its own drops popup. Showing players the odds makes the grind feel fair, which is a lesson we took straight from running the full server: players accept grind when they can see the math.
What's next
Music, a gentler early cost curve, and original art. If you've shipped an incremental game, how did you decide your cost multiplier? Was it spreadsheets, or vibes and playtesting?
Try the work-in-progress build at snowrsps.com/io (free, no sign-up). If you like the custom-loot flavour, the full SnowRSPS server is at snowrsps.com.
Top comments (0)