How the club earns with the tab closed without melting your phone
Eight offline hours are not simulated second by second. They are worked out in one go, and the result has to match what someone with the game open would have got.
The request that sounds simple
In an idle game, the central promise is this: you close the game, live your life, come back later and the club has earned. It is what makes the genre exist. And it is where an engineering problem hides that is not obvious at first.
The game advances state in small steps, around 100 milliseconds, because that is how a progress bar moves smoothly on screen. Now do the arithmetic: eight hours is 28,800 seconds, which means 288,000 steps. If opening the game simply ran the same loop 288,000 times, the device would freeze for several seconds on a blank screen, and on a weaker phone the browser would kill the tab.
Worse: someone away for two days would wait even longer. Startup time would grow with the absence, which is precisely backwards, because the longer you were away, the more you want to see the result now.
The answer is not to simulate
The fix is to compute the result in one go, in constant time, without iterating. If a production cycle lasts 3 seconds and 8 hours have passed, there is no need to count cycles one by one: you can divide.
// the idea, without the details
const cycles = Math.floor((remaining + dt) / duration)
const earned = perCycle.times(cycles)
const left = (remaining + dt) % durationThree lines handle any interval: five seconds and two days cost the same. That is what we call constant-time advance, and it is the property that makes offline earnings possible without punishing whoever stayed away longer.
The hard part is not the formula. It is guaranteeing it keeps producing the same result as the long route, because now there are two paths to the same state, and two paths that silently diverge are the worst category of game bug: the player with the tab open and the player who came back later would hold different balances, and nobody would know which one is right.
The proof is an equivalence test
The project has a test that does exactly this: take a state, advance eight hours in one go; take the same state and advance 288,000 steps of 100 milliseconds. Then compare.
And here comes the detail the post about large numbers already hinted at: the two results are not identical. The cycle count is, because a cycle is a whole number. The cash differs by around 10^-15 relative, because adding 288,000 small slices rounds differently from adding one large amount.
Cycles are compared for exact equality. Cash is compared within a tolerance. Using equality for both gives a test that fails correct code; using tolerance for both lets a cycle-counting error through, and that is a real error.
Two rules the test enforces that are not obvious
First: a cycle duration has to be a whole number of milliseconds. If a business paid out every 3.33 seconds, the division and remainder would stop matching between the two routes, and equivalence would break for a reason nobody would find by reading the code.
Second: time arrives as a parameter. No line in the game core calls the system clock. The layer outside knows what time it is and passes the instant inwards. That sounds bureaucratic until you need to test eight hours of play: with the clock inside the code, that test would take eight hours.
The same rule covers randomness. No loose random calls: the transfer market and match results come from a seeded generator, which makes the same save produce the same sequence on any device. That is what later allows a server to check whether a ranking was played or invented.
The cap, and why it is said out loud
Offline accumulation is capped at eight hours. That is not a technical limit: it is a game decision, because an idle game that earns forever on its own stops giving you a reason to come back.
What matters here is how it is presented. The first version only showed the earnings, so someone away for two days saw a number that did not match the absence, with no explanation. It looked like a bug. Today the return report states both things: how much was credited and how much fell outside the cap. And the warning about the limit appears before it ever charges, on the first return, not only on the return where the player has already lost something.
It is a product rule that travels well beyond this game: a limit that only reveals itself at the moment it punishes reads as a defect, even when it is intentional.
You can also just play.
Play free now