TC401: Avoidance Diminishing Returns in WoD

Avoidance and diminishing returns are topics we’ve spent a fair amount of time discussing in the past. In 2012, I wrote a series of threeblogposts discussing avoidance diminishing returns in Mists of Pandaria, chronicling the background of the formulas the community uses, numerically fitting data sets to determine the diminishing returns coefficients, and eventually discussing what those equations mean for gearing. We used similar fitting techniques to find values for druids and monks.

Later that year, we used a more precise data set collected with the Statslog addon to fine-tune the values for paladins and uncover some wonky rounding in the block calculation. And in 2013, we performed an exhaustive analysis including lots of pretty surface plots to get accurate values for all of the tank specs.

First, some quick tests uncovered a bug in the calculation of base parry, which Blizzard subsequently fixed. And in the past two weeks, I’ve discovered another oddity, which I’ve detailed below.

As usual with TC101 articles, I’ll give the results up front for reference (and those that don’t want to scroll to the bottom to get them), and then start talking about how we performed the tests.

What’s Changed

The diminishing returns (DR) formulas for WoD have changed very little from the Mists versions. In fact, the most significant changes are to the contributions that are not affected by DR.

In Mists, base strength and agility add an amount of parry or dodge, respectively, that is unaffected by DR. In Warlords, that contribution has changed in the following ways:

Strength no longer grants parry for druids and monks, and agility no longer grants dodge for paladins, warriors, and death knights.

They have subtracted out the “non-DR” parry and dodge contributions from base strength and agility, such that base parry and dodge should be exactly 3.00% (the default for all players and NPCs) plus any race- or spec-based passives. For example, a night elf would have 5.00% base dodge thanks to Quickness, as would a paladin thanks to Sanctuary. However… there are a few quirks.

They have subtracted out the contribution due to the class portion of base strength, but not the portion due to the racial strength modifier. That racial strength modifier’s contribution is also not affected by DR. So a gnome warrior (-5 racial strength modifier) would have 2.97% base parry (-0.0283% due to the -5 strength racial modifier). The same is true for the racial agility modifier’s contribution to dodge.

Finally, there’s probably a small error in the strength subtraction, because all strength tanks (regardless of race) are getting a “phantom” 0.0004178% parry, or about 0.0739 strength worth of parry, that isn’t affected by diminishing returns.

The last quirk is the interesting one, because it’s a bug. Not a game-breaking bug, mind you; we’re talking about about 0.0004% parry. For most people, they’ll never even be perceptible.

Nonetheless, it’s worth noting because it’s just barely large enough that it could cause a slight discrepancy between calculated and character sheet parry values if not accounted for. For example, if your theorycrafted parry is 21.0199%, but the actual value is 21.0203%, your character sheet will read 21.02% instead of 21.01% (as we mentioned last post, character sheet values are almost always floored, not rounded).

I’ve informed Blizzard of the bug, but I’ve been told that given their programmer’s workload and the tiny magnitude of the effect, we should not expect to see it fixed.

Warlords of Draenor Diminishing Returns Formulas

Since Blizzard has kindly provided us with the diminishing returns coefficients, I’ve chosen to use their formula rather than the community’s traditional one. In the next section I’ll show how to convert the coefficients provided by Blizzard into the conventional ones you might be familiar with.

In the following equations, $\text{ParryFactor}$, $\text{DodgeFactor}$, $\text{BlockFactor}$, $\text{VerticalStretch}$, and $\text{HorizontalShift}$ are the class-specific Blizzard DR coefficients given in the table at the end of this section. $Q_S$ and $Q_A$ are the level-dependent and class-specific strength-to-parry and agility-to-dodge conversion factors. $\text{classBaseStr}$ and $\text{classBaseAgi}$ are class-dependent base stat values, and $\text{raceStrMod}$ and $\text{raceAgiMod}$ are the racial stat modifiers. $\text{Strength}$, $\text{Agility}$, $\text{Mastery}$, $\text{parryRating}$, and $\text{dodgeRating}$ are your character sheet values for each of these stats.

Note that I’m giving these equations in percent form. In other words, a $\text{bonusParry}$ of 15.3% is 15.3 in the equation. If using decimal form (e.g. $\text{bonusParry}=0.153$) there is an extra factor of 100 in the term with $\text{VerticalStretch}$. The easiest way to accommodate this is to just multiply $\text{VerticalStretch}$ by 100.

Blizzard’s formula for diminishing returns looks a little different than the one the community generally uses. If you read community guides (including my old posts on the subject), we tend to use a two-parameter formula:

where $C_p$ is the parry cap (or dodge cap $C_d$ or block cap $C_b$ for the other formulas) and $k$ is a class-dependent constant. It’s not hard to show that this formula is identical in form to the Blizzard ones: multiply both numerator and denominator of the second term on the right-hand side by $\text{bonusParry}$ to get:

and similarly for $C_d$ and $C_b$, substituting the appropriate “Factor” constant. There’s also a fourth $C_m$ and $\text{MissFactor}$ that I’ve omitted since we don’t have ways to change our miss chance.

Despite the fact that Blizzard’s formula has three parameters, each individual formula only really uses two. $\text{ParryFactor}$ and $\text{VerticalShift}$ are redundant parameters, since they only ever show up multiplied together. In theory, they could eliminate one of them entirely in favor of the other; e.g. eliminate $\text{VerticalShift}$ and merge that into the $\text{Factor}$ variable. Then every equation would have a different $\text{Factor}$ variable (just as ours had a different cap constant $C_p$, $C_d$, etc.), but the same class-based $\text{HorizontalShift}$.

Comparing the tables, it’s clear that our fitting system nailed all of these. Most were accurate out to 4 decimal places, and the remaining ones out to three decimal places. Almost all of them were accurate to the reported precision in the table. That gives us a lot of confidence in our fitting algorithms and techniques, which is why we’re able to believe that we can detect a systematic error of 0.0004% parry. Now, let’s see how we figured that out.

Testing The Parry Equation

Based on several discussions with Celestalon, we know that the original intent was something like this:

In other words, the parry from the class contribution to base strength (which is not affected by DR) is completely negated, such that base parry is exactly 3% for each class. However, we also discovered that this wasn’t the case, and were subsequently told that the racial strength modifier is not negated, and that this racial modifier is affected by DR. The “edit” and follow-up post he’s provided since are actually in error, as we’ll see shortly.

So, taking his word from before the edits, what we should be seeing is:

If you plug those values into the formulas above, you get (keeping only 10 decimal places):

2.9703430316%

If you use GetParryRating() in-game though, you get (again, to 10 decimal places):

2.9720702171%

A difference of ~0.002%. Not that far off… but this should be nearly exact, we’re using the same formula the game does. At first, I thought that perhaps this was because Celestalon rounded the $\text{ParryFactor}$, $\text{VerticalStretch}$, and $\text{HorizontalStretch}$ values he gave us in the Theorycrafting thread. However, last week I took a few quick data sets to test this hypothesis, and convinced myself that isn’t the issue. In fact, given the accuracy and agreement we see with the empirically-obtained values from August 2013, the values he provided in the Theorycrafting thread may well be exact, and not rounded at all!

To illustrate why, here’s the data for the naked gnome warrior, all retrieved through the the Statslog addon with minor tweaks to get it working again on beta. Statslog uses the World of Warcraft API functions UnitStat("player",1), GetCombatRatingBonus(CR_PARRY), and GetParryChance() to retrieve this information, giving us very high precision results. Again, I’ve only kept 10 decimal places for the table since we’d be ecstatic to fit to even that precision. To collect this data, I just added or removed gear to change my total stats.

Gnome Warrior Data

$\text{Strength}-\text{classBaseStr}$

$\text{parryRating}/162$

$\text{totalParry}$

-5

0.0000000000

2.9720702171

-5

1.2962962389

4.3203210831

205

1.2962962389

5.5452437401

426

2.2037036419

7.7356786728

550

2.7530863285

8.9868812561

674

2.7530863285

9.6833515167

798

3.3024692535

10.9137287140

964

3.9814815521

12.4860315323

1130

3.9814815521

13.3895244598

1351

4.8888888359

15.4365730286

1517

5.6172838211

16.9933910370

1738

6.5246915817

18.9761753082

1904

6.5246915817

19.8289909363

2028

7.0000000000

20.8874912262

2249

7.0000000000

22.0019493103

2121

6.4753084183

20.8898067474

2245

6.9876542091

21.9709510803

I put that data in MATLAB and used it to fit values for $\text{VerticalStretch}$ and $\text{HorizontalShift}$ with very strict tolerances on accuracy but very loose tolerances on $\text{VerticalStretch}$ ($\pm 0.0001$) and $\text{HorizontalShift}$ ($\pm 0.001$). This means that MATLAB’s fitting algorithms will attempt to determine the values very accurately, but will also allow them to vary anywhere within the tolerance on each constant. Note that I’ve chosen those tolerances to provide complete flexibility in rounding those values and then some – in other words, any value of those constants which rounds to the ones given by Celestalon is fair game in this fit, and even some values outside of that range (e.g. 0.00666 for $\text{VerticalStretch}$).

The formula, fit details, and plot are given below. In this fit, $x$ is our definition of $\text{bonusParry}$ above, which includes the strength contribution (in this case negative) of the racial strength modifier.

That looks pretty good – hs ($\text{HorizontalShift}$) is about right, though the parameter vs ($\text{VerticalStretch}$) has to be higher than is reasonable to be rounded to a value of 0.00665. However, the proof that something’s wrong is in the plot of the residuals – the difference between the fitted curve and the actual data. Here are those differences in tabular form:

Gnome Warrior parry fit residuals

Fit

Data

Residual

2.9703

2.9721

-0.0017

4.3190

4.3203

-0.0013

5.5442

5.5452

-0.0010

7.7352

7.7357

-0.0005

8.9866

8.9869

-0.0003

9.6832

9.6834

-0.0002

10.9137

10.9137

-0.0000

12.4862

12.4860

0.0001

13.3897

13.3895

0.0002

15.4369

15.4366

0.0003

16.9937

16.9934

0.0003

18.9763

18.9762

0.0002

19.8291

19.8290

0.0001

20.8875

20.8875

-0.0000

22.0018

22.0019

-0.0002

20.8898

20.8898

-0.0000

21.9708

21.9710

-0.0002

Again, it’s not off by much… but ~0.002% is enough to cause rounding errors that lead the character sheet value to differ by 0.01% compared to calculations (i.e. SimC’s output). So it’s worrisome. Furthermore, these residuals tell us something else about the fit. A graphical representation is a little more revealing, in this case:

Gnome warrior parry fit residuals.

It may not be clear to a layperson, but someone who’s been fitting data for years will instantly recognize the meaning of that plot. The fact that there’s curvature indicates some sort of systematic error. If we have the correct formula, the residual plot shouldn’t have curvature – it should look almost like a random scatter plot. For example, like this plot from 2012:

Dodge residuals from August 2012 post.

If you read through that post, you’ll notice that I used the same technique of looking for details and patterns in a residuals plot to identify a systematic error in our assumed value of the agility-to-dodge conversion factor, as well as to figure out that the game performs a binary rounding operation on $\text{bonusBlock}$.

The point this time around is that with such loose tolerances on the two DR coefficients, the fit should have no trouble producing a very clean residual plot. That tells us that either

My first thought was to try the obvious thing: assume Celestalon lied! Ok, not really, but I thought maybe he was mistaken about the racial stat modifier being affected by DR. So I moved it outside the DR calculation in my code. In other words, I now let

You might note that the residuals have gone down considerably here – the largest is now $-4\times 10^{-4}$ rather than $-1.7\times 10^{-3}$, which suggests that the hypothesis is likely correct. The parry from racial strength modifiers is probably not being affected by diminishing returns. But what bothered me is that there’s still curvature on the residuals plot – that means we still don’t have the formula right.

Just to make sure I wasn’t loony, I tried this with dwarf and draenei warriors too. Both races exhibited similar curvature in the residuals plot, so it’s not just gnomes acting oddly. Next, as a sanity check, I tried a human warrior. Since a human warrior has a racial modifier of zero, the curvature should disappear if that’s the cause of the problem. I also added $Q_S$ as another fit parameter just in case that value was incorrect, unlikely as that was since it was also given to us by Celestalon. Adding that parameter turns our curve fit into a surface fit, so the residual plot will be 3-dimensional. Here’s what it looked like:

The curvature there should be clear despite the viewpoint of the plot. So this curious result tells us that this isn’t a problem limited to racial strength modifiers. There’s still something else missing. And it didn’t take much guessing to stumble across the solution.

Let’s assume there’s some extra amount of strength that’s giving parry, and that it’s not subject to diminishing returns. In other words, we let

$\text{baseParry} = 3.00 + (\text{raceStrMod} + R)\times Q_S$

Let’s also assume that $Q_s$ is accurate as given, so we can go back to curve fits rather than surface fits. Then, the fit and residuals for a human warrior look like this:

This is what a residual plot should look like. It’s mostly numerical noise at the ~$1\times 10^{-6}$ level, and it’s more or less randomly distributed. It’s not entirely clear why we even have that much noise, because $\text{HorizontalShift}$ and $\text{VerticalStretch}$ are allowed to vary by $\pm 0.001$ and $\pm 0.00001$ respectively, so it’s not due to rounding of those values. It may just be some subtlety of the calculation that differs between Blizzard’s implementation and my MATLAB formula. Regardless, it isn’t that important – nobody will ever notice a 0.000001% change in parry chance.

In any event, the key point here is that Humans are magically getting around 0.07385 Strength worth of parry chance (about 0.0004187%) that isn’t affected by DR. The likely explanation is that the subtraction that negates the parry from $\text{classBaseStr}$ isn’t quite correct, since we know they’ve been fiddling with that recently. It may be something as simple as rounding – if whatever formula determines a warrior’s base strength produces a value of 1455.07385, the game could be adding 1455.07385 strength worth of parry rating, but only subtracting off the rounded 1455 strength worth of rating.

Repeating this with our gnome (-5 racial strength modifier) or dwarf (+5 racial strength modifier) revealed a few more details. If the racial strength modifier was included in $\text{bonusParry}$ (and thus affected by DR), I was only able to get a fit with “good” residuals if I was flexible with $\text{HorizontalShift}$ and $\text{VerticalStretch}$ – for example, if I let $\text{VerticalStretch}=0.00661$ and $\text{HorizontalShift}=0.9562$. And that was further complicated by the fact that those values would have to be allowed to be different for each race, which is obviously wrong since we know they’re class-dependent constants that are independent of race. If I tightened the restrictions on $\text{HorizontalShift}$ and $\text{VerticalStretch}$ such that they are nearly exact as given, the curvature started to appear again. For example:

Again, recall that the $\text{bonusParry}$ value $x$ I feed to this equation is adjusted for the location of the racial modifier in the code in each case.

Note the amount of “phantom” strength $r$ being added here: 0.07391, awfully close to the value we found for humans. As it turns out, we get the same amount of “phantom” strength if we fit a gnome death knight (0.07387) or a dwarf warrior (0.07382), and a very similar value from a draenei warrior (0.07369). Loading up a human paladin (and adjusting $\text{HorizontalShift}$ appropriately) gives a phantom strength value of (0.07392), confirming that this isn’t a class-based thing and doesn’t depend on the DR calculation at all.

In short, any tank class that gets a strength-to-parry conversion is getting this phantom amount of parry, regardless of race, DR coefficients, or gear. Testing with a monk shows that they have a fixed 3.0000000% parry, suggesting that the agility tanks are not getting this bonus. This is why I’ve framed it as a phantom amount of strength rather than a phantom amount of parry. We can’t say for sure if the agility tanks still have that phantom strength contribution or not, because either way they don’t get any parry from it.

Testing The Dodge Equation

The obvious next question was whether agility tanks were getting a similar effect.

To test, I just assumed that the racial agility modifier worked the same way as the strength modifier did for the strength tanks. I created a Night Elf monk, which has a +2% dodge racial bonus and +4 racial agility modifier, and tried the formulas:

Looks like our hunch was correct, and that racial base agility is granting dodge that isn’t affected by diminishing returns. Just to be sure, we should test a few more times though. Let’s double check this with a dwarf monk, which has a racial agility modifier of -4, and a base parry of 3%:

This pretty much confirms that we’ve got it right, and it’s reasonable to expect that it works the same way for the rest of the druid and monk races.

Testing The Block Equation

There isn’t really much to say here. The block equation is unchanged from Mists, and it appears they haven’t tinkered with it. The exact same fitting code that worked in Mists works now, and produces a great fit. Nonetheless, I updated it to match the other functions and to use the Blizzard formulation:

Similarly good fits were obtained with all of my warrior test subjects, suggesting that the block DR equations are working the same way they have been.

Conclusions

So we’ve confirmed that

Racial strength modifiers grant parry that is not affected by parry DR.

Racial agility modifiers grant dodge that is not affected by dodge DR.

There’s some phantom parry being added to all strength-based tanking classes, but not to agility tanking classes.

Block diminishing returns seem to be working exactly like they should be.

The obvious question is, “what will Blizzard do about it?”

The parry bug is a difference of 0.0004%, which is insignificant in the grand scheme of things. It only matters to crazy people like me that want to be able to replicate the character sheet values 100% of the time and stamp down those rare 0.01% rounding errors. So it didn’t surprise me in the least that the response was that I should just build it into my models, because it was too small to matter and their programmers had better things to do. I can’t argue with that at all, really.

Similarly, I don’t think there’s any reason they need to move the racial strength modifier back out of $\text{baseParry}$ and into $\text{bonusParry}$, or to remove it altogether. It’s entirely arbitrary how that works, and it only matters insofar as theorycrafters want to know how to model it properly. It seems like a waste of time for them to change that around at this point just for the sake of cleaning up the equations.

So I think it’s safe to say that these are the diminishing returns formulas that we’ll be using throughout Warlords. I’ve already programmed the changes into Simulationcraft and run some tests with a few characters to make sure they’re working.

A Word On Theorycrafting

This installment was a little more complicated than the earlier TC101 articles. In particular, I talked a lot about “fitting” the data, but didn’t go into any detail on how that’s done. However, explaining how to fit data using MATLAB’s curve fitting toolbox or associated methods would be somewhat useless, because the likelihood is that you don’t have MATLAB at home. It’s more likely that you’d be putting the data in Excel or a Google documents spreadsheet and attempting to fit the data that way.

Unfortunately, that way is a lot less flexible (which is why I use MATLAB instead!). The built-in fitting functions are more limited, for one thing. If your data is linear, then you’re all set, but if not you often have to be creative. You can’t quickly and easily define and change a custom fitting function, at least to my knowledge.

But there are plenty of tutorials on how to do this sort of thing online. Fitting linear (i.e. $y=mx+b$) or polynomial data (i.e. $y=a + bx + cx^2 + dx^3 + …$) is incredibly easy, and you’ll find plenty of hits from a simple Google search. Fitting nonlinear data, like our diminishing returns equations, is more complicated, but there’s a great guide from California State Polytechnic University, Pomona that details how you’d go about fitting a complicated equation in Excel.

Ultimately, though, your first step should be to ask yourself whether you need that level of precision. For something like this, most players don’t, and would be satisfied with being within 0.01% of the character sheet avoidance value. If you decide you do need more accuracy, that’s when you take stock of the tools you have at hand to deal with the problem, and decide if they’re suitable. If they are (maybe you have Excel and the time to learn how to use the Solver), then great! If not, though, you might need to seek out someone who does have the knowledge and/or tools you need.

Remember, theorycrafting is a collaborative process, so there’s nothing wrong with asking for assistance. Sometimes just having another set of eyes looking at the data, or looking at it with a different tool, will crack a tricky problem wide open.

re: people not having MATLAB. GNU Octave is syntax-compatible with MATLAB, and has many of the same libraries. I haven’t used it myself for much since linear algebra classes, but it’s GPLed and well maintained. http://www.gnu.org/software/octave/

To make the Block Formula work, we need how much Mastery Rating is required for 1% block. In trying to find this number I have seen 110 quoted but I have also seen it stated that it is class specific. Do you happen to know?