Ecru GamesDevlog › A multiplier that fed itself

A multiplier that fed itself

One evening I added a multiplier that was, by every reasonable reading, correct. Three days later there were save files holding numbers past the edge of float precision, and a player was explaining the exploit to other players in the chat. Nothing in my test suite could have caught it, and that is the part worth writing down.

Ecru Games developer

Cotton Tycoon has a prestige system, the standard incremental-game bargain: sell the company, lose everything, keep a permanent multiplier. Points are earned from the value of the company you sold, and the reward card promises roughly "+6% production" per point.

Production, in that game, includes the farm. So when I noticed that the prestige multiplier was applied to the factory but not to the size of a harvested bale, it read like an oversight. A bale is production. The card says production. I added it.

What I did not draw

The chain looks like this once you write it out:

money → prestige points → bale size → more cotton → more money

Prestige points are bought with company value. Company value comes from money. Money comes from selling cotton. Bale size now came from prestige points. The loop closed, and every trip around it multiplied.

The loop already existed in a weaker form, because the multiplier fed the factory rate, and that is fine: a factory that runs faster still needs cotton it does not have. The farm was the missing input side. Connecting it turned a damped loop into a compounding one.

A player described the result in the chat before I found it: "if you do the company sale, you'll have trillions in about 15 to 20 minutes." Three save files reached prestige values between 1013 and 1023, which is outside the range where floating point arithmetic behaves like the numbers a player sees.

Why every check passed

This is the uncomfortable part. That change went out having passed:

None of them could have caught it, and not because they were badly written. A measurement is a photograph of one instant. Feedback grows over time, by definition. The multiplier looked correct in every frame I photographed and only misbehaved across hours of play, in the hands of players who had already reached the part of the game where the loop had fuel.

The check I added instead: when introducing a multiplier, trace every term its input reads, backwards. If any of them arrives at one of the output variables, money, company value, level, stock, there is a loop. Then you either break the connection or add saturation. This is a review question, not a test, because no test observes it.

Damping something that is already exponential

The first brakes were the obvious ones: diminishing returns on the points earned, and a ceiling on the multiplier. Both helped and neither was enough, for a reason that took a second round to see.

The gain formula raised company value to the power of 0.55. That looks like diminishing returns, and against a linear input it is. The input was not linear. A company worth 38.7 trillion is 19.3 million times the step size, and the root of a number that large is still 10,178 points. A dampener applied to an exponential input is not a dampener, it is a slightly slower exponential.

The same shape had already bitten me in the levelling system. Payout across 109 saves grows as roughly L4.15, a square root turns that into L2.07, and a level costs about L2. The two exponents cancel, so the rate never falls. Measured across 89 saves, levels per delivery were flat from level 34 all the way to level 527. The suppression was working exactly as designed and achieving nothing.

Two capped things can still multiply

The second round found that the production multiplier had a ceiling but the price multiplier did not, and the two are multiplied together. The real revenue advantage of the most extreme save against a fresh farm was ×371.

Measured across the 28 real prestige owners: players who had sold their company were gaining levels 2.3 times faster per hour than those who had not, with the fastest at 142 levels an hour. Selling was the dominant strategy by a wide margin, and it stayed invisible because only 14% of players were willing to delete their farm to find out.

What I changed, and what I deliberately did not

I did not remove anyone's points. Editing player save files is irreversible, hits legitimate players alongside the extreme ones, and requires a migration. Changing the function instead reaches every save at once, needs no migration, and lets a player keep seeing the number they earned.

The production cap came down, the price multiplier got saturation rather than a hard stop, and the gain curve got a second knee at 200 points, which is a company worth 30 billion, a threshold two saves in the game had ever crossed.

SaveRevenue multiplier beforeAfter
Extreme 1×371×99
Extreme 2×338×91
Extreme 3×161×59
Extreme 4×112×54
Median owner (28 points)×5.0×5.0

That last row is the acceptance test. Anyone below 50 points saw no change at all, and the gain curve is identical below its knee, so existing points were untouched and only the next sale is priced differently. Someone who deletes a level 300 farm has to be rewarded for it. The tail got clipped, the mechanic did not.

A hard cap was avoided on purpose. A wall the interface does not draw is a wall players spend money on without knowing, and that mistake has its own long history in this game.

One more thing the offer card was saying

While fixing the curve I found that the offer card multiplied its projected bonus by a hard-coded constant, written back when both multipliers were linear. For a player with 1,141 points it would have advertised +61,104% production. The true value was +0%, because their production had been sitting at the ceiling for weeks.

The card now borrows the level, calls the function the simulation calls, and prints whatever comes back, including an honest "+0%" when there is nothing left to buy. An interface that computes its own version of a number will eventually disagree with the game, and the player will believe the interface.

Play Cotton Tycoon