Critical chance in video games is not calculated how you think. Video games have a sophisticated way of applying probability, and it's called pseudorandom distribution (PRD).

Uniform distribution

You already know what a uniform distribution is. It's your intuitive idea of probability where every outcome is equiprobable. The simple example is a d10; no side has a higher chance to land than any other. This creates a swingy distribution where outcomes could land high or low without skew.

In video games, a uniform distribution can generate Bernoulli (binary) outcomes. For example, let's say {1, 2, 3, 4, 5, 6, 7, 8, 9} are non-crit values and {10} is a crit value. There's an underlying uniform distribution, but our selection process for the outcome is Bernoulli. This is just a fancy way of saying we can have only two types of outcomes. However, players find the outcomes frustratingly unpredictable.

Consider a crit chance of 10%; intuition says you will crit 1-in-10 times. This is wrong. In a uniform distribution, you could miss crit 20 times in a row. The chance of a 20-hit streak without crit is 12.16%, a near 1-in-8 chance. Humans underestimate the chances of streaks, and eventually experience disappointment.

This pain point is solved via PRD, a trick to make probability feel more fair.

Pseudorandom distribution

Say you have a desired crit chance p=10%p^* = 10\%. PRD can help you crit 1-in-10 times more consistently. Instead of having a 10%10\% crit chance on every hit, your initial crit chance is 1.47%1.47\%. After each missed crit, you gain a flat constant of +1.47%+1.47\%; this stacks until a crit is guaranteed. Once you crit, it resets back to 1.47%1.47\% again. PRD serves as a pity mechanic, eventually guaranteeing the desired outcome. You can be unlucky now, but you will never be unlucky forever. Sophisticated, no?

The equation is P(n)=CnP(n)=Cn, capped at the smallest nn where P(n)1P(n)\ge1. P(n)P(n) is crit chance, nn is the number of hits since the last crit, and CC is the additive increment to crit chance per consecutive miss. Note, CC is also the initial crit chance. On proc, nn resets to 1.

Now, you might be wondering how we got CC. Well, we have to solve for CC backwards.

Solving for the PRD constant

CC is the PRD constant. There's only one exact CC for each pp^*, but the smaller CC is, the more rows you'll have to compute. 1%1\% crit requires computing 6,409 rows. 50%50\% crit requires only 4 rows. For now, let's try finding CC for 10%10\% crit.

Example. Let's say I want to find CC where p=10%p^* = 10\%.

Recall that P(n)=C×nP(n) = C \times n.

  1. Guess and check C=0.10C = 0.10.

  2. P(n)=0.10×nP(n) = 0.10 \times n

  3. Find the hit number where crit is guaranteed (MM).

    • M=1/C=1/0.10=10M = \lceil 1/C \rceil = \lceil 1/0.10 \rceil = 10
  4. Find the chance to reach each hit nn without crit. You can use one of the two following equations; they're equivalent. I use the recursive form.

    Product form.

R(n)=(1P(1))×(1P(2))××(1P(n1))R(n) = (1 - P(1)) \times (1 - P(2)) \times \cdots \times (1 - P(n-1))

Recursive form.

R(n+1)=R(n)×(1P(n))R(n+1) = R(n) \times (1 - P(n))

hit nn R(n)R(n) calculation R(n)R(n)
1 11 1.0000001.000000
2 1×(10.10)1 \times (1 - 0.10) 0.9000000.900000
3 0.900000×(10.20)0.900000 \times (1 - 0.20) 0.7200000.720000
4 0.720000×(10.30)0.720000 \times (1 - 0.30) 0.5040000.504000
5 0.504000×(10.40)0.504000 \times (1 - 0.40) 0.3024000.302400
6 0.302400×(10.50)0.302400 \times (1 - 0.50) 0.1512000.151200
7 0.151200×(10.60)0.151200 \times (1 - 0.60) 0.0604800.060480
8 0.060480×(10.70)0.060480 \times (1 - 0.70) 0.0181440.018144
9 0.018144×(10.80)0.018144 \times (1 - 0.80) 0.00362880.0036288
10 0.0036288×(10.90)0.0036288 \times (1 - 0.90) 0.000362880.00036288
  1. Find crit chance for hit nn.

f(n)=R(n)×P(n)f(n) = R(n) \times P(n)

  1. Find average hits per crit.

    Calculate the weighted average n×f(n)n \times f(n) for every nn.

E[n]=1(0.10)+2(0.18)+3(0.216)+4(0.2016)+5(0.1512)+6(0.09072)+7(0.042336)+8(0.0145152)+9(0.00326592)+10(0.00036288)=3.66021568\begin{aligned} E[n] &= 1(0.10) + 2(0.18) + 3(0.216) + 4(0.2016) + 5(0.1512) \\ &\quad + 6(0.09072) + 7(0.042336) + 8(0.0145152) + 9(0.00326592) + 10(0.00036288) \\ &= 3.66021568 \end{aligned}

  1. Find the actual crit chance.

p=1/E[n]=1/3.66021568=0.27320827.32%p = 1/E[n] = 1/3.66021568 = 0.273208\ldots \rightarrow 27.32\%

Here's the comprehensive table:

hit (nn) crit chance P(n)P(n) chance to reach nn R(n)R(n) chance crit procs on nn (f(n)f(n)) n×f(n)n \times f(n)
11 10%10\% 100%100\% 10%10\% 0.100.10
22 20%20\% 90%90\% 18%18\% 0.360.36
33 30%30\% 72%72\% 21.6%21.6\% 0.6480.648
44 40%40\% 50.4%50.4\% 20.16%20.16\% 0.80640.8064
55 50%50\% 30.24%30.24\% 15.12%15.12\% 0.7560.756
66 60%60\% 15.12%15.12\% 9.072%9.072\% 0.54430.5443
77 70%70\% 6.048%6.048\% 4.234%4.234\% 0.29640.2964
88 80%80\% 1.814%1.814\% 1.452%1.452\% 0.11610.1161
99 90%90\% 0.363%0.363\% 0.327%0.327\% 0.02940.0294
1010 100%100\% 0.036%0.036\% 0.036%0.036\% 0.00360.0036

Sum of n×f(n)n \times f(n): E[n]=3.66E[n] = 3.66 (average hits per crit) \rightarrow actual crit chance p=1/E[n]=27.32%p = 1/E[n] = 27.32\%

Well damn, we didn't find CC for 10%10\% crit. We found CC for 27.32%27.32\% crit.

Did we do something wrong? No, we did everything right; we're just not finished yet. You know whether your guess for CC is too high or too low by calculating the actual crit rate pp and comparing it to desired crit rate pp^*. Then, we'll set an interval [a,b][a, b].

  • If p>pp > p^*, then CC is too high. Set the interval [a,C][a, C].
  • If p<pp < p^*, then CC is too low. Set the interval [C,b][C, b].

Our CC for 27.32%27.32\% crit is too high, but we know that CC for 0%0\% must be too low, so the answer must exist in [0,0.10][0, 0.10].

In the inverse case, if CC were too low, we know that CC for 100%100\% would be too high, so the answer would exist in [0.10,1][0.10, 1].

Bisection

I'm not going to check every number in this interval; I'm going to bisect it. Bisection is uber simple, just slash the interval of candidate values in half at the midpoint, calculate pp, compare pp and pp^* values, and keep the half that still contains the answer. Repeat this, and we converge towards the answer.

For each iteration kk, find the midpoint Ck=ak+bk2C_k = \dfrac{a_k + b_k}{2}.

C0=0+0.102=0.05.C_0 = \dfrac{0 + 0.10}{2} = 0.05.

Using C0C_0, I calculate for pp again, and we get p=18.89%p = 18.89\%.

At this point, I continue iterating until we converge to the answer.

This is the bisection recurrence and midpoint equations.

Ck={Ck1b0a02k+1if pk1>pCk1+b0a02k+1if pk1<pC_k = \begin{cases} C_{k-1} - \dfrac{b_0-a_0}{2^{k+1}} & \text{if } p_{k-1} > p^* \\[0.5em] C_{k-1} + \dfrac{b_0-a_0}{2^{k+1}} & \text{if } p_{k-1} < p^* \end{cases}

C0=a0+b02C_0 = \frac{a_0+b_0}{2}

where pk1p_{k-1} is the actual crit chance produced by Ck1C_{k-1}.

Iteration table

iteration kk interval [ak,bk][a_k,b_k] midpoint CkC_k actual crit rate pp comparison to pp^*
00 [0,0.10][0, 0.10] 0.0500000.050000 18.8908%18.8908\% high
11 [0,0.05][0, 0.05] 0.0250000.025000 13.1421%13.1421\% high
22 [0,0.025][0, 0.025] 0.0125000.012500 9.1845%9.1845\% low
33 [0.0125,0.025][0.0125, 0.025] 0.0187500.018750 11.3208%11.3208\% high
44 [0.0125,0.01875][0.0125, 0.01875] 0.0156250.015625 10.3031%10.3031\% high
55 [0.0125,0.015625][0.0125, 0.015625] 0.01406250.0140625 9.7585%9.7585\% low
66 [0.0140625,0.015625][0.0140625, 0.015625] 0.014843750.01484375 10.0342%10.0342\% high
77 [0.0140625,0.01484375][0.0140625, 0.01484375] 0.0144531250.014453125 9.8972%9.8972\% low
88 [0.014453125,0.01484375][0.014453125, 0.01484375] 0.01464843750.0146484375 9.9659%9.9659\% low
99 [0.0146484375,0.01484375][0.0146484375, 0.01484375] 0.014746093750.01474609375 10.00009%10.00009\% high

By iteration 9, we've converged to the answer:

C90.01474609375=1.474609375%.C_9 \approx 0.01474609375 = 1.474609375\%.

For any real-world use case, I would consider using a lookup table over calculating the values in runtime. While runtime calculation is already very fast, you can ensure consistency across networks by preventing weird floating point divergences.

If you're serious about precision, bisect 65 times; this will saturate float64. At this precision, precomputing C for every integer in [1,100][1, 100] will still be 5 times faster than the visible flash of lightning (lightning flash 301ms\approx 301\,\mathrm{ ms}, precomputation on my machine 60ms\approx 60\,\mathrm{ ms}).

Curious whether I'm right? Don't be; see it for yourself.

Computing the PRD table for [1, 100]

from math import ceil
from time import perf_counter


def find_prd_constant(desired_crit_chance):
    if desired_crit_chance == 1:
        return 1.0

    low, high = 0.0, 1.0

    for _ in range(65):
        C = (low + high) / 2
        p = actual_crit_chance(C)

        if p > desired_crit_chance:
            high = C
        else:
            low = C

    return (low + high) / 2


def actual_crit_chance(C):
    average_hits = 0.0
    reach_chance = 1.0

    for n in range(1, ceil(1 / C) + 1):
        crit_chance = min(C * n, 1.0)
        proc_chance = reach_chance * crit_chance

        average_hits += n * proc_chance
        reach_chance *= 1 - crit_chance

    return 1 / average_hits


results = []
elapsed_ms = 0.0

for percent in range(1, 101):
    start = perf_counter()
    C = find_prd_constant(percent / 100)
    elapsed_ms += (perf_counter() - start) * 1000

    results.append((percent, C))

print(f"{'p':>3}  {'C':>4}")

for percent, C in results:
    print(f"{percent:3}%  {C * 100:>12.8f}")

print(f"\nDone in {elapsed_ms:.3f} ms")

PRD table for [1, 100]

Rounded to the nearest 0.001% via banker's rounding.

desired pp^* constant CC desired pp^* constant CC desired pp^* constant CC desired pp^* constant CC
1 0.016 26 9.118 51 31.268 76 68.421
2 0.062 27 9.783 52 32.329 77 70.130
3 0.139 28 10.467 53 33.412 78 71.795
4 0.245 29 11.171 54 34.737 79 73.418
5 0.380 30 11.895 55 36.040 80 75.000
6 0.544 31 12.638 56 37.322 81 76.543
7 0.736 32 13.400 57 38.584 82 78.049
8 0.955 33 14.181 58 39.828 83 79.518
9 1.202 34 14.981 59 41.054 84 80.952
10 1.475 35 15.798 60 42.265 85 82.353
11 1.774 36 16.633 61 43.460 86 83.721
12 2.098 37 17.491 62 44.642 87 85.057
13 2.448 38 18.362 63 45.810 88 86.364
14 2.823 39 19.249 64 46.967 89 87.640
15 3.222 40 20.155 65 48.113 90 88.889
16 3.645 41 21.092 66 49.248 91 90.110
17 4.092 42 22.036 67 50.746 92 91.304
18 4.562 43 22.990 68 52.941 93 92.473
19 5.055 44 23.954 69 55.072 94 93.617
20 5.570 45 24.931 70 57.143 95 94.737
21 6.108 46 25.987 71 59.155 96 95.833
22 6.668 47 27.045 72 61.111 97 96.907
23 7.249 48 28.101 73 63.014 98 97.959
24 7.851 49 29.155 74 64.865 99 98.990
25 8.474 50 30.210 75 66.667 100 100.000

Addenda