If you had the chance to play with the new RPPM trinkets on PTR (or already got one on live !), you have probably experienced how extremly random they can feel... to the point that you may even have considered getting rid of them because they are just too unreliable. The fun fact is that the RPPM system has been around for quite some time with weapon enchants, and these felt OK in that regard. Is that just a false impression ?
This post is an analysis of why the statistics hiding behind the RPPM system, and a proposal of an alternative design that keeps its spirit ("procs should be random, not predictable") but discards the frustration ("4 minutes, still no proc").
TL;DR :
- Is it just a false impression that 0.5 RPPM trinkets feel waaay less reliable than say, weapon enchants ?
Not at all. Actually, a 0.5 RPPM trinket is about twice as "random" as a 2 RPPM weapon enchant.
- Are there alternative designs to address that "high randomness" issue without going back to the totally predictable ICD system ?
Yes. One of the simplest (Smoothed RPPM) is thoroughly described and analyzed here. Smoothed RPPM cuts randomness by a factor ~2 for usual RPPM rates.
Accessible explanation of the results
Rezoacken did a great job at writing down a clear and simple explanation of the results I got that anyone can understand. You definitely should read this first, especially if you're not too fond of math. If you want to know how I obtained these results, read the analysis section !
Mathematical analysis
Analysis of the RPPM system
For every event that can trigger a RPPM-based proc, the chance that this actually happens is :
P = R * H * (t - t0) / 60
Where :
- R is the base RPPM rate (in procs per minute).
- H = (1 + h%) is the haste factor.
- t is the current time (in seconds).
- t0 is the time of the last event that had a chance to trigger the proc.
For this whole analysis, I will assume the following to keep calculations simple :
- Constant haste.
- Constant interval between events that can trigger the proc : (t - t0) = delta_t.
- No special situations like the pull, these will be discussed separately.
Under these assumtions, each event has a constant chance delta_P of triggering the proc :
delta_P = R * H * delta_t / 60
To further simplify the analysis, we will make the assumption that delta_P << 1, i.e. the interval between attacks is small compared to the expected interval between procs. This will allow us to use a continuous model that is mathematically easy to manipulate, and very well known.
The continuous approximation
To analyze the statistical properties of the RPPM system, we need to describe the law of the following random variable T = "time between two procs".
Under the continuous assumption (delta_P << 1), dP[t < T < t+dt | t < T] = (R*H/60) * dt
Which means that T follows an exponential law of parameter L = R*H/60, i.e.
dP[t < T < t+dt] = L*exp(-L*t)
As anticipated, the expectation of T is :
E[T] = 1/L = 60/(R*H).
(For example, with 0% haste and a base rate of 0.5 RPPM, the average time between procs is 2 minutes.)
And the variance of T is Var[T] = 1/L², which means that the standard deviation is :
Std_dev[T] = sqrt(Var[T]) = 1/L
Std_dev[T] = E[T] = 60/(R*H).
This gives us a first hint as to why RPPM-based proc tend to feel more random with low base rates : as R goes down and E[T] goes up, the probability distribution of T widens proportionnaly.
Analyzing the number of procs over a whole fight
Let's consider the big picture now: from the law of T, we can infer the distribution of N = "Number of procs over a fight" (for a fixed fight duration F in seconds). Indeed, the arrival of procs follows a poisson process, which means that that N follows a poisson distribution of rate U = L*F = R*H*F/60, namely :
P[N = k] = (U^k) * exp(-U) / k!
Again, it's higly interesting to look at the expectation, variance and standard deviation of N :
E[N] = U = R*H*F/60. (Intuitive. For example : without haste, a 1 RPPM effect will proc 6 times on average over a 6 minutes fight)
Var[N] = U.
Std_dev[N] = sqrt(U).
But the "randomness" of an effect is not measured by the raw standard deviation of N, but rather by its relative standard deviation Std_dev[N] / E[N].
And in this case :
Randomness[N] = Std_dev[N] / E[N] = 1 / sqrt(U)
Randomness[N] = sqrt(60/(R*H*F))
And we see that the "randomness" of an effect increases as the base RPPM rate R decreases.
Smoothed RPPM, an alternative to the RPPM system
Design description
In order to reduce the randomness of the RPPM system while keeping procs unpredictable, let's define a slightly different mechanic. In this system, each event that can trigger the proc has the following chance to actually do so :
P = A * (t - t1) * R * H * (t - t0) / 60
Where :
- R is the base RPPM rate (in procs per minute).
- H = (1 + h%) is the haste factor.
- t is the current time (in seconds).
- t0 is the time of the last event that had a chance to trigger the proc.
- t1 is the last time the proc actually triggered. We will consider it to be 0 to keep the equations clear.
- A is a normalization constant that we will precisely define later.
Under the same hypothesis (of constant haste and equally spaced attacks) as for the RPPM system, and posing C = A*R*H/60 :
delta_P = C * t * delta_t
Analysis of the time between procs T
Assuming again delta_P << 1, we can make the same continuous approximation.
dP[t < T < t+dt | t < T] = C * t * dt
Note : this approximation is not as good as the precedent because C * t * delta_t will eventually increase to some non << 1 value if no proc occurs, but this corresponds to a very unlikely situation if C * delta_t << 1.
If we define the function g(t) = P[T > t] :
g(t+dt) / g(t) = 1 - C * t * dt
<=> (g(t+dt) - g(t)) / dt = - C * t * g(t)
<=> g'(t) / g(t) = - C * t
<=> (log g)'(t) = - C * t
<=> (log g)(t) = const - C * t²/2
<=> g(t) = const * exp(-C * t²/2)
And since g(0) = 1, the constant is simply 1.
We can then derive the probability distribution of T : f(t) = P[t < T < t+dt] / dt
f(t) = (g(t) - g(t+dt)) / dt = - g'(t)
f(t) = C * t * exp(-C * t²/2)
We can then compute the expectation of T :
E[T] = Integral(0, infinity)[t * f(t) * dt]
E[T] = Integral(0, infinity)[2u² exp(-u²) du] * sqrt(2 / C) [with variable change u = sqrt(C/2)*t]
E[T] = sqrt(2/C) * ValueBetween(0, infinity)[u * exp(-u²)] - sqrt(2/C) * Integral(0, infinity)[exp(-u²) * du] [with per part integration, integrating 2*u*exp(-u²) and derivating u]
E[T] = 0 - sqrt(2/C) * sqrt(Pi) / 2
E[T] = sqrt(Pi / (2*C))
Since the underlying RPPM design is that we should wait 60 / (R*H) seconds on average between procs, we can compute the value of the normalization constant A :
E[T] = 60 / (R*H) = sqrt(Pi / (2*C))
<=> 60² / (R² * H²) = 60 * Pi / (2 * A * R * H)
<=> A = Pi * R * H / (2 * 60)
Thus, the actual proc chance formula is :
delta_P = C * t * delta_t
C = (Pi/2) * (R*H/60)²
We can also derive the variance and standard deviation of T :
Var[T] = E[T²] - E[T]² = E[T²] - Pi/(2*C)
E[T²] = Integral(0, infinity)[t² * f(t) * dt]
E[T²] = Integral(0, infinity)[C * t^3 * exp(-C*t²/2) * dt]
E[T²] = Integral(0, infinity)[u^3 * exp(-u²) * du] * (4/C) [with variable change u = sqrt(C/2)*t]
E[T²] = (2/C) * ValueBetween(0, infinity)[u² * exp(-u²)] - (2/C) * Integral(0, infinity)[2 * u * exp(-u²) * du] [with per part integration, integrating 2*u*exp(-u²) and derivating u²]
E[T²] = 0 - (2/C) * ValueBewteen(0, infinity)[exp(-u²) * du]
E[T²] = 2/C
Var[T] = 2/C - Pi/(2*C)
Var[T] = (4 - Pi) / (2*C)
Std_dev[T] = sqrt(2 - Pi/2) / sqrt(C)
As with the RPPM system, this yields a constant relative standard deviation Std_dev[T] / E[T]. However, this constant was 1 with the RPPM system, while for this system it is :
Std_dev[T] / E[T] = sqrt(4/Pi - 1) ~= 0.523 < 1
Here is an of the probability density of T for R*H = 1 ppm, with the RPPM system (blue) vs. with the smoothed RPPM system (green).
More graphs for various values of R*H !
Analysis of the total number of procs over a fight N
Given the complicated nature of the distribution of T, it is much more complicated to come up with a direct formula for P[N = k] and thus derive the expectation, variance and "randomness" of N. It is however possible to run numerical simulations to estimate these parameters and see how they evolve with R.
Here is an illustration of the probability distribution of N for R*H = 1 ppm and F = 360s, with the RPPM system (blue) vs. with the smoothed RPPM system (green).
More graphs for various values of R*H !
Here are graphs of the expectation, standard deviation and relative standard deviation of N as a function of R*H, for a 360s long fight, with the RPPM system (blue) vs. with the smoothed RPPM system (green).
These graphs show that the Smoothed RPPM system effectively reduces the "randomness" of proc effect without affecting its expectation. With "usual" low values of R*H (0.5 - 1), the randomness is reduced by a factor ~2.
As a side note, it is interesting to notice that we can actually "reverse engineer" a proc chance formula of the form dP = h(t) * dt for any target probability density f(t) for T.
Indeed, if g(t) = P[T > t], then we can compute g from :
g'(t) = f(t)
g(t) = Integral(0, t)[f(u) * d(u)]
And we then have :
h(t) = - g'(t) / g(t)
The smoothed RPPM system is however one that is easiest to implement and most importantly simplest to understand, which is why it is highlighted that much.
What happens at the pull & after long breaks ?
It is important to consider how the formula will behave not only in the middle of a long fight, but also in edge situations like the pull time, or after a relatively long break with no attacks. The RPPM way of handling it is to let delta_t "stack up" to 10 seconds and cap it then.
The Smoothed RPPM system has so to say one more degree of liberty to choose over :
- The time since the last chance to proc delta_t.
- The time since the last actual proc t.
One way of handling edge cases would be the following :
- In combat, let delta_t stack up to X seconds where X is a small number still large enough that one shouldn't cap when uninterruptedly attacking. (10 seconds like with the RPPM system seems like a decent value).
- Upon entering combat, set :
- delta_t = 0 (or a very low value like 1s to still give a chance to proc on the first hit)
- t = min(t, B * E[T]) (to make sure there is no guaranteed proc on the first hit)
Depending on the value of B, one can decide on whether it should be frequent that effects proc quickly after the pull or not. A value of 0 gives very little chance of getting a proc immediately (as if you had just got one, it's unlikely you will get a second one right afterwards with smoothed RPPM), while high values of B almost ensure a very quick proc.
Here is an example of the probability distribution of the time to first proc for R*H = 1 ppm and B = 0.5.
Here are graphs for more values of B !
Here is a graph of the expected time to first proc as a function of B, for R*H = 1 ppm.
Hope you enjoyed the read
Please comment on the issue and share any kind of feedback about the explanation or the math itself !
Ah and here is the Matlab I code used to draw all those nice graphs !