DEV Community

Cover image for I set an 18-hour playtime target for my idle game. A simulation says it has 3.7
Dhardingsea Developer
Dhardingsea Developer

Posted on

I set an 18-hour playtime target for my idle game. A simulation says it has 3.7

Ashline is an idle army-vs-army battler I'm building for my browser arcade (not public yet). You get five unit roles, enemy waves that hit back, offline progress, a champion with skill trees and a collection of officers you recruit.

Before putting it in front of people, I wanted a number instead of a feeling. How long does a realistic player take to run out of new things to unlock? My target was 18 hours.

Simulating a player instead of guessing

I already had a headless engine and a reference player for balance testing, so playtime.js puts the two together. The simulated player plays 20 active minutes a day, closes the tab and comes back tomorrow. It records the moment each of the game's 20 achievements first unlocks.

Offline time isn't a multiplier. It runs the actual combat loop at reduced efficiency, so being away advances waves. That's capped at 8 hours.

// the shape of it (simplified)
while (unlocked.size < ACHIEVEMENTS.length && day < MAX_DAYS) {
  for (let t = 0; t < 20 * MIN; t += TICK) {
    referencePlayer(S);            // buy, rally, recruit, prestige
    tick(S, TICK);
    activeTime += TICK;
    for (const a of ACHIEVEMENTS)
      if (!unlocked.has(a.id) && a.test(S)) unlocked.set(a.id, activeTime);
  }
  settleOffline(S, DAY - 20 * MIN); // real combat, reduced efficiency, capped
  day++;
}
Enter fullscreen mode Exit fullscreen mode

The first version hung. The bug wasn't in the game. My reference player had unbounded while loops for recruiting and spending points. They're capped at 200 iterations now.

The result

  • 9 of 20 achievements unlock in the first 40 minutes.
  • All 12 officers are collected by 1.3 hours.
  • The last unlock lands at 3.7 active hours.

That's about five times short of the target.

The obvious fix is the wrong one

The quickest way to hit 18 hours is to multiply every threshold. Same unlocks, just further apart.

That adds grind, not playtime. The simulation measures when you run out of new things, and stretching the thresholds doesn't create any. The gap is content.

So I added content, and the number got worse

The biggest addition was Onslaught, a separate mode for the champion:

  • 12 named waves, then Endless
  • one life, and no full heal between waves
  • after every cleared wave, pick one of three boons drawn from a pool of 24
  • boons last only for that run
  • marks (the champion's progression currency) are banked when you die

I tuned it so a fresh champion dies around wave 2 in about 30 seconds, and an invested one reaches 20+.

Then I re-ran the playtime simulation: 3.1 hours, down from 3.7.

Onslaught banks marks, and marks buy the champion progression that unlocks achievements. So the new mode wasn't a new ladder. It was an accelerator bolted onto the old one. The achievements are still tied to the army's wave counter. A new mode needs goals of its own, like run depth and build variety, or it only shortens the path to the same finish line.

Why "no full heal"

In the main mode, when your army hits 0 HP it routs: it falls back two waves, heals fully and keeps going. An idle game shouldn't dead-end while you're away.

The balance suite exposed a side effect. I had written a test claiming "the champion alone cannot progress." That claim is false by construction. With a full heal after every rout, anything with regeneration can grind forever. I replaced it with a comparative test (the army must out-progress a lone champion by a margin) and built Onslaught so that death actually ends something.

Splitting the modes quietly broke two tests

Taking the champion out of army combat left two balance bands meaningless, and one of them still looked great:

  • "No dominant skill tree" reported 90 / 90 / 90 / 90. Perfect balance, apparently. In fact, the four trees no longer touched army combat, so every build was identical.
  • The spell band read 0 / 0, because spells no longer buffed the army.

I retired both rather than leave them green. Eight Onslaught-specific bands replaced them: the stakes are real, investment helps monotonically, the ladder is beatable, no boon dominates, no boon is dead, death is final, marks scale with depth, and a draft follows every clear.

Two more things measuring caught

Correlated randomness. Recruiting rolls a rarity, then an officer within that rarity. Both rolls used the same index into the seeded generator, so they were correlated: 7 out of 7 rares came out as the same officer. Deriving a separate index fixed it, and I checked the fix over 10,000 pulls. Pulls stay deterministic on (seed, pull number), so reloading a save can't reroll a result.

An optimization I didn't make. Officer effects get recomputed several times per tick, and I had flagged it for memoization. Profiling put it at 5.69µs per tick, 0.034% of a 16.7ms frame. Shipping that "fix" would have been claiming a win that didn't exist, so I cut it.

What's next

  • Achievements tied to Onslaught depth and build variety, so the new mode extends the ladder instead of shortening it.
  • Real attack classes. The five army roles currently fight identically. Only their HP, armor, regen and splash differ, while the names and the 2.5D ranks suggest melee, ranged and magic. That's the biggest gap between how the game looks and how it plays, and it's an engine change that invalidates every balance band, so it gets its own tuning pass.
  • Then re-run playtime.js.

The number I didn't want was the most useful thing the simulation produced. 3.7 hours doesn't say the game is bad. It says where the game ends, and that adding a mode isn't the same as adding somewhere to go.

Top comments (0)