DEVLOG · GAME DESIGN

Multiplier Fatigue: fixing runaway scores without nerfing the fun part

22 AUGUST 2026

Number Run: Pro Edition's gates do simple arithmetic to your orb's value — add, subtract, multiply, divide. Early on, a lane of consecutive ×2 and ×3 gates could take a score from three digits to unreadable in about a second and a half. Technically nothing was wrong; two ×3 hits in a row really is ×9. The problem was that the run stopped being interesting the moment a player found a lane of multipliers and just held it, because every additional hit was worth doubly-exponentially more than the last.

The trap: don't touch what a gate says it does

The tempting fix is to just weaken multiply gates — make ×3 mean ×2.4, or cap the value a multiply can reach. Both of those break something more important than score pacing: this is an exact-target game. Every level has a precise numeric target, and the level's solution path is generated assuming a ×3 gate multiplies by exactly 3. Quietly changing that value mid-run would make levels mathematically unsolvable, silently, which is worse than a runaway score.

The actual fix: separate what the ball's value does from what the run's score does

The two were sharing one multiplier before this change. Splitting them apart made the rest of the problem tractable:

With that split in place, fatigue could apply a decaying bonus multiplier to score, specifically and only on consecutive multiply hits, without ever being able to make a level unsolvable:

val fatigueMultiplier = if (op.type == OpType.MUL) {
    (1f / (1f + max(0, mulStreakIndex - 1) * GameConfig.MUL_FATIGUE_DECAY_PER_HIT))
        .coerceAtLeast(GameConfig.MUL_FATIGUE_FLOOR)
} else 1f

An isolated ×3 hit — one multiply gate with adds and subtracts around it — still lands at full score value. It's only a chain of back-to-back multiplies that gets diminishing score returns, decaying toward a floor rather than to zero, and the streak resets itself after a short cooldown (MUL_FATIGUE_RESET_FRAMES) so a player who naturally spaces out their multiply hits never even notices the system exists.

A hard ceiling as the actual last line of defense

Fatigue changes incentives, but it doesn't prevent a determined chain of multiplies from eventually pushing the ball's value absurdly high — and it shouldn't have to, because the ball value is deliberately untouched by it. So there's a separate, unrelated hard cap: GameConfig.MAX_PLAYER_VALUE. If the ball's value ever reaches it, gameplay freezes for a short countdown with a clear "MAXIMUM VALUE REACHED" warning, then the value resets to whatever it was at the start of the current level. That path should be effectively unreachable through normal play — it exists purely so an edge case can never corrupt the run state instead of trusting fatigue alone to prevent it.

What this bought

The mechanical meaning of every gate stayed exactly what it says on the tile — a promise the game can't afford to break. Score pacing got fixed by discouraging one specific pattern (spamming multiplies back-to-back) without discouraging multiply gates themselves, which are still one of the most satisfying things to hit in the game. And a genuinely unreachable failure mode got a real, if extremely unlikely, backstop instead of an assumption.

← Back to Devlog