It’s been quite some time since we last looked at warrior mechanics. While this is primarily a paladin-centric blog, I like to dabble in other classes from time to time, specifically when it seems like there’s something I’m uniquely suited to contribute. That’s not to suggest there’s no warrior theorycrafting going on – certainly Tankspot and Elitist Jerks both have active theorycrafting discussions. What they both tend to lack, however, is rigorous simulation-based modeling. Simcraft is certainly quite accurate for warriors, but the interface seems to mostly focus on DPS results. If there’s a way to analyze damage intake patterns, I haven’t found it yet. And in any event, it’s always nice to have multiple, independent tools to verify results.
Numerical modeling is sort of my area of expertise when it comes to WoW, and especially given how similar the interests of protection warriors and paladins are, it seemed like spending some time to refine those earlier warriors simulations was justified. I thought it was going to be a rather quick project and another one-off blog post. But the deeper I got into it, the more interesting it became, eventually ballooning into a significantly larger and more time-consuming enterprise (unfortunately forcing me to push back several other, more paladin-specific projects as a result). There’s no way that a single blog post would end up doing it justice at this point.
What follows is therefore the first of a series of posts critically analyzing the different options available to protection warriors. You may remember that my initial stabs at this problem were fairly limited. First, we looked at mitigation stat weights by using a Monte-Carlo similar to the one I wrote for paladins and a very simple “spam Shield Block” model for finisher usage. In the follow-up, I tried to address how Shield Barrier changes the stat priorities. But there were a number of serious limitations to both of those models. The goal of this series will be to eliminate those limitations, improve the model so that it’s sufficiently accurate, and see what it tells us.
Limitations of earlier modeling
The first and foremost limitation was the modeling of finishers. Excluding Shield Barrier entirely was obviously not a great real-world model, and the “bleed” strategy implemented in the follow-up was at best a guess at proper usage. It was suitable enough for simple TDR simulations, but really doesn’t accurately model how warriors claim to actually use Shield Barrier when they play.
A further wrinkle is that there doesn’t seem to be a consensus on what does constitute optimal Shield Barrier usage. There’s been a variety of suggestions, and a lot of discussion in the comments of the last post and on a number of forums. The most common idea presented was to “weave” Shield Barriers in during the downtime of Shield Block in an attempt to increase smoothing. But there’s a lot of hand-waving in those suggestions and not a lot of specifics. One of our objectives in these posts will be to try and quantify the specifics of that weaving process.
Many people found it surprising that mastery fared so well in the earlier models, and it ended up being a fairly major point of discussion. I read a lot of comments expressing surprise that hit and expertise weren’t significantly better than the model predicted. A lot of that surprise was based on misinterpretation though. Remember that the earlier models were calculating total damage reduction (TDR) stat weights, and while those are interesting numbers, they’re also not always the most relevant. For example, dodge and parry have the highest TDR stat weights for paladins, and hit and expertise are abysmal at TDR. Yet we pretty much ignore dodge and parry completely in favor of hit and expertise because we’re more interested in damage smoothing than TDR. So it’s not really surprising at all that hit and expertise aren’t killer TDR stats – there’s no expectation that they should be (and arguably, they shouldn’t be from a design standpoint if dodge and parry are to fill any niche).
This has led to some conjecture that the real power of hit and expertise is in the smoothing department. After all, they’re amazing smoothing stats for Paladins, so it stands to reason that they may perform similarly for warriors. To see that, though, we need more sophisticated metrics. Calculating raw TDR-based numbers isn’t enough. Thus, this time around, we’re going to thoroughly analyze the spike damage numbers (much like I’ve done for paladins) in addition to TDR.
First, let’s go over the details of the model. The files I’ve used can be found on the matlabadin repository:
warr_mc_finisher is just a script file that calls warr_mc multiple times with different options (i.e. different queues). warr_mc is the guts of the simulation. It’s a pretty standard Monte-Carlo style simulation which handles all of the combat rolls, tracks buff uptimes, spell cooldowns, and the global cooldown, chooses spells according to priority queues, and so on. And of course, it spits back a bunch of statistics about the run that we can use later to determine what’s going on.
The code simulates 10000 minutes of combat for each trial in 0.5-second time steps. This is long enough to get good, statistically stable results while still running relatively quickly. Smaller time steps are not necessary for warriors as there are no haste-scaling effects to consider, and using a larger time step vastly decreases execution time. To give you an idea of scale, it takes about 45 seconds to run a single trial – considerably faster than the 300-400 seconds it takes to run the paladin version of the code, which needs to use much smaller time steps.
The simulation will be using the file’s “default” gear set, which is based on a character with an average ilvl of about 496. It’s been adapted from my paladin simulations, so it has the same 11k strength and 17150 points worth of secondary stats, but they’re allocated more appropriately for a warrior. The exact stat allocation is as follows:
Parry Rating: 3000
Dodge Rating: 3000
Mastery Rating: 3000
Hit Rating: 2550
Expertise Rating: 5100
I’ve chosen to use hard hit and expertise caps for this set because one of the arguments for hit and expertise is that it allows more Shield Barrier use. By allocating to reach the caps, we put ourselves in the best possible situation for Shield Barrier, which should help us avoid bias introduced by being unnecessarily rage starved. Once we’ve nailed down our “winning” finisher queues in this blog post, we can go back and try different stat allocations in a follow-up post. The gear assumes the Eternal Primal Diamond is equipped and activated, and properly accounts for all stat conversions and diminishing returns (DR) calculations. This set of stats gives you about 20.9% block chance, 31.4% critical block chance, 8.4% dodge, and 17.7% parry on the character sheet. Note that against a boss, block, dodge, and parry are all 4.5% lower than the character sheet value.
The boss we’ll be using in the model is a Patchwerk clone with a 1.5-second swing timer and a raw DPS output of 310k for Vengeance purposes. That means that the boss’s raw swing is 465k, which is about 150k damage after armor (52%), spec (25%), and Weakened Blows (10%) mitigation effects. Vengeance is assumed to be static at the average value, which is a steady-state contribution of 124k AP. The average 60-rage Shield Barrier absorbs 277k at this level of Vengeance and armor mitigation, or a little over 1.8 full boss swings worth of damage. Note that all the damage in this simulation is physical at the moment – I plan on implementing a periodic magical damage source for a future blog post to see what happens, but it was simply too much complexity to add to an already long blog post.
The file contains two different “generator queues,” as I’ve described in earlier blog posts. One is a “steady-state” queue that casts spells according to the following priority:
Thunder Clap is only cast if Weakened Blows isn’t active. Berserker Rage is also included in the simulation, and is cast essentially on cooldown, but since it’s off-GCD it isn’t part of the generator queue. The second queue is a “short” queue that ignores Thunder Clap entirely to optimize for short, 30-second tanking intervals. I’ve used the steady-state queue for everything in this post, as the differences are fairly negligible. The cost of Thunder Clap is only about 0.03 rage per second (RPS) – less than 0.5% of our total rage income, which clocks in at around 8.1 RPS. This generator queue agrees with most theorycrafting sources (EJ and Tankspot), and the RPS value is consistent with what I get from Airowird’s Spreadsheet. I have also assumed that the warrior is using the Glyph of Unending Rage, so we have a more relaxed rage cap of 120.
The simulation tracks and records a great number of details for debugging and post-processing, but for analyzing the data presented below we only need to describe a few of them. First of all, it records Shield Block uptime, which will be given in percentage and labeled “S%.” It also records the normalized damage registered by each boss swing (i.e. 0 for an avoid, 0.69 for a block, 0.38 for a critical block, 1 for a full hit). In post-processing, we perform a number of calculations on this string of damage events.
The simplest is to calculate the mean of damage intake, which we express in a normalized fashion as a percentage of maximum damage intake. To clarify that a bit, the absolute worst case you’ll ever see is if you took every attack straight to the face (no blocks, avoids, absorbs, etc.). That’s the maximum theoretical DTPS, so we call that 100% of maximum DTPS. If instead you blocked every attack (no crit blocks, no avoids, etc.), it should be pretty clear that you’d be taking 69% as much damage as if you took every attack to the face. And subsequently, the mean damage intake is 69% of the maximum possible DTPS in this normalization scheme.
When you have a mix of blocks, critical blocks, avoids, absorbs, and regular hits, it gets more complicated to calculate, but that’s what computers are for – MATLAB does all the hard work for us. This normalized damage percentage it spits out gives us a nice, clear, understandable number that represents effectiveness. If we want to talk about TDR, it’s understood that taking 55% percent of max possible damage is better than taking 60% or 70%, for example.
We also use this damage string to calculate a moving average DTPS in order to evaluate spike damage events, much like we’ve done for paladins. The statistics we report for these moving averages are the percentage of events that fall above 80% and 90% of maximum theoretical intake. This is a rough measure of the percentage of the time you’ll be subject to a dangerous spike event. Since it’s not always clear what number of attacks we’re interested in, I’ve posted results for everything from a 2-attack moving average to a 7-attack moving average so that we can evaluate each independently.
I’ve also reported the standard deviation of spike damage intake to give us some idea of the variation about the mean. In all cases I’ve used the 5-attack moving average to calculate the standard deviation, as it seems like a fairly reasonable spike window to care about (7.5 seconds) for a 1.5-second swing timer.
Finally, I’ve reported some additional statistics that are unrelated to the damage event string. For each queue I’ve reported the number of Shield Barrier casts in thousands (“SBr(k)”) as well as the number of those casts that consume less than 60 rage (“SBr<60(k)”). The reasoning for this will be clearer once we start looking at specific queues. I’ve also reported the average generated rage per second for each simulation and the “excess rage” in thousands (“xsR(k)”), which is the amount of rage wasted by casting generators that put us above 120 rage. For example, if we cast Shield Slam at 110 rage, we only gain 10 rage and add 10 to the “excess rage” counter.
Now that we’ve gone over all of that, let’s dive into the results.
The first set of queues we want to check are the “basic” ones that exclusively use either Shield Block or Shield Barrier, but not both. We don’t expect these to be our ideal queues, but they’re useful to determine certain general trends and for use as a benchmark for later queues. The logic in use for each of these is as follows:
- SB is just a simple “spam Shield Block if rage>=60″ finisher queue.
- SBr is the complementary “spam Shield Barrier if rage>=20″ queue.
- SBr* is simply SBr plus the conditional that the Shield Barrier buff isn’t active – i.e. we refuse to overwrite an existing Shield Barrier buff.
- SBr60 simply raises the rage threshold of SBr to 60.
- SBr60* is just SBr60 plus the refuse-to-overwrite conditional
As for how these queues sim out:
| Set: | SB | SBr | SBr* | SBr60 | SBr60* | | S% | 0.6667 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | mean | 0.5853 | 0.4864 | 0.4152 | 0.4203 | 0.4154 | | std | 0.1396 | 0.1780 | 0.2028 | 0.2269 | 0.2251 | | SBr(k) | 0.0000 | 206.4450 | 100.0000 | 73.6000 | 73.5750 | | SBr<60(k) | 0.0000 | 206.4450 | 66.3290 | 0.0000 | 0.0000 | | RPS | 8.1027 | 7.3572 | 7.3596 | 7.3600 | 7.3576 | | xsR(k) | 861.4570 | 0.0000 | 0.0000 | 0.0000 | 0.0000 | | ------ | --- 2 | Attack | Moving | Average | ------ | | 80% | 23.3978 | 14.7177 | 17.2875 | 22.6947 | 21.9303 | | 90% | 7.9937 | 10.5173 | 8.5653 | 17.8223 | 17.2885 | | ------ | --- 3 | Attack | Moving | Average | ------ | | 80% | 9.1665 | 3.1238 | 5.5723 | 12.2955 | 11.7200 | | 90% | 0.0000 | 2.2260 | 3.9820 | 8.4932 | 8.1530 | | ------ | --- 4 | Attack | Moving | Average | ------ | | 80% | 7.8185 | 5.2713 | 2.2310 | 6.5980 | 6.2198 | | 90% | 0.0000 | 0.0000 | 0.0000 | 5.4610 | 5.1498 | | ------ | --- 5 | Attack | Moving | Average | ------ | | 80% | 5.9015 | 2.1840 | 2.0305 | 4.4545 | 4.2405 | | 90% | 0.0000 | 0.0000 | 0.0000 | 1.9775 | 1.8755 | | ------ | --- 6 | Attack | Moving | Average | ------ | | 80% | 0.0000 | 0.2325 | 1.0405 | 2.9575 | 2.8235 | | 90% | 0.0000 | 0.0000 | 0.0000 | 0.4120 | 0.3885 | | ------ | --- 7 | Attack | Moving | Average | ------ | | 80% | 1.1783 | 0.5647 | 0.4083 | 0.7910 | 0.7565 | | 90% | 0.0000 | 0.0000 | 0.0037 | 0.0175 | 0.0118 |
First of all, we note that the Shield Barrier queues are significantly ahead on total damage taken. That isn’t surprising – at this level of Vengeance, a max-rage Shield Barrier absorbs about 1.8 boss attacks. A single Shield Block cast can cover at most 4 attacks, and only guarantees 31% mitigation on each attack, or roughly 1.24 boss attacks. That’s not accurate though, because some of those attacks may be avoided, and about 1 in 5 of them would have been blocked anyway. The guaranteed block effect increases the mitigation of critical block, but that barely offsets the blocking losses. In general, Shield Block will give you closer to about 1 boss attack worth of mitigation (and gets worse with more avoidance).
Conversely, avoidance makes Shield Barrier better, because an avoid doesn’t waste Shield Barrier’s absorption potential. It does have a small effect, in that multiple avoids in a row could cause you to waste a Shield Barrier buff by not taking enough damage, but that’s relatively rare and thus has a very small effect. This synergy between Shield Barrier and avoidance is interesting, because it’s very reminiscent of an early beta implementation of Shield of the Righteous. It’s a positive feedback loop in which each one makes the other better.
In any event, the imbalance in the damage mitigated to rage spent ratio is a pretty significant factor. The best Shield Barrier queue (SBr*) takes almost 30% less damage than the Shield Block queue, which is a pretty huge amount of TDR. However, the comparison isn’t entirely fair, as the SB queue wastes a significant amount of rage – it only takes 6.667 RPS to maintain maximum Shield Block uptime, and anything over that is wasted without another rage sink to spend it on.
What we’re really interested in is the spike data though. And this is where Shield Block really shows its strength – it is 100% effective at removing 90% spikes for strings of 3 or more attacks. This should be obvious by inspection – you get 12 seconds of Shield Block uptime every 18 seconds, and this naturally falls into a 6-seconds-on, 3-seconds off cycle. In 3 seconds, you’ll only ever see 2 boss attacks at this swing speed, so you’re always guaranteed to block at least one out of every 3 attacks. And the relative damage intake of that is (0.69+1+1)/3=0.8967, or 89.6%.
It does less well for 80% spikes in short windows, but pretty quickly pares that down as well. For 6-attack strings, it eliminates all of the 80% spikes thanks to guaranteed block coverage. They pop up again for the 7-attack strings, which might seem surprising, but it’s just a result of combinatorics – as we increase string length, we get a periodic oscillation in the number of unblocked swings. The moving average slowly damps away to zero as it rides this oscillation because of statistical averaging and avoidance (more swings = less likely to have zero avoids).
It’s also worth noting that most of these 80% events are right on the borderline, as in exactly 80%. A little more passive mitigation and most of them might shift down below the 80% threshold, giving us much better results for SB. We’ll see that more clearly on the histograms, though.
The SBr60 queues don’t do very well. They have the characteristic lower TDR, but otherwise they only eclipse SB in one or two isolated instances, specifically in 80% spikes in the 4-, 5-, and 7-attack categories. But the associated cost is an unacceptable amount of 90% spikes in all categories. And for short attack strings they’re simply abysmal. So we can safely rule them out as desirable queues.
The curious (and perhaps surprising) result here is how well the low-rage Shield Barrier queues perform. Both the SBr and SBr* queues are very competitive with Shield Block in the spike damage categories. They both all but eliminate 90% spikes for 4+ attacks and best SB in the 80% category for strings of 5 attacks or less. It’s only for 6 attacks that Shield Block is clearly better, but SBr/SBr* have an edge in the 7-attack category as well.
I was also a bit surprised that the buff overwriting in the SBr queue doesn’t seem to hurt it too much. While it isn’t broken down completely in the table, SBr casts almost exclusively 20-rage Shield Barriers (93% are 20 rage, 7% are 40 rage, none are 60 rage), while SBr* has a more varied mix (33% are 60 rage, 53% are 40 rage, and the remaining 13% are 20-rage casts). One would expect that to mean we overwrite a 20-rage buff with another 20-rage buff fairly frequently, thus wasting a lot of potential. And while it does carry a fairly significant cost in TDR, it doesn’t seem to hurt smoothness metrics. In fact, it even improves some of them, especially for short attack strings.
I think what’s happening here is that since a 20-rage Shield Barrier absorbs about 60% of a boss swing, we’re getting situations where a critically-blocked attack won’t clear the Barrier buff. SBr* would leave that weak, ~20% remaining Barrier in-tact so as to not waste any potential absorption, but SBr will over-write it with a new Barrier. Thus, SBr will be significantly stronger against 2-attack strings, but for longer strings it won’t be much better than SBr*. In fact, it seems like the two keep jockeying for position on odd and even numbers of attacks, suggesting we have another combinatorics effect going on here.
In fooling around with some numbers, SBr performed much more poorly than SBr* at higher armor values, where a single Shield Barrier cast covers a larger portion of a boss attack. Conversely, SBr performed better at lower armor values. So SBr seems to be more dependent on armor than SBr* is, making it a little more volatile. It’s also worth noting that both Barrier queues are fairly sensitive to armor. In the sims I’ve run at lower armor values, the absorption of Shield Barrier was about 20% weaker, and these queues ended up falling strictly behind SB. That extra 20% was enough to make a noticeable difference in the strength of a single Shield Barrier cast. Note that the armor-Barrier synergy we’re seeing here is very similar to the avoidance-Barrier synergy I spoke about earlier – armor makes each mitigated hit less, thus making Shield Block weaker compared to Shield Barrier. This could explain the coming nerf – Shield Barrier is scaling a little faster than it should thanks to its synergy with armor and avoidance.
As a final piece of analysis, let’s look at the damage histograms for SB, SBr, and SBr*. This is a plot of the 5-attack moving average DTPS that we’re using to calculate our 80% and 90% statistics. Basically, it shows the distribution of damage spikes – lots of events up in the 80%-100% region means lots of spikes, fewer events in that region means fewer spikes.
The SB plot looks similar to the ones we’ve seen for the “control/haste” build for paladins. Events up at the top end are heavily suppressed, but we still have a chunk (about 6% of all events) right on the borderline at 80%. This is what I was talking about earlier; if we had set the arbitrary threshold at 81%, Shield Block would’ve looked incredible on the tables. In any event, most of the damage is clustered between 50% and 80%, giving us the nice, smooth, predictable damage intake that we like.
The SBr plot has a wider distribution, as is evidenced by the larger standard deviation. We also have a larger representation of spikes in the ~87% range, nearing the 90% danger threshold. But the relative dearth of 60-rage barrier casts means that we have relatively few full absorbs. Most of the damage is falling between 35% and 75% of max throughput.
The SBr* queue has an even wider distribution, thus a significantly larger standard deviation. We get a lot more full absorbs and low-percentage strings with SBr*, which makes our damage a lot spikier. That said, the dangerous spikes are fairly limited – we have a few percent in the 80%-90% window, and nothing above 90%.
It’s worth noting that both Shield Barrier queues are going to have spikier damage intake than the Shield Block queue thanks to the broader distribution. However, it’s not clear that a wider spread necessarily means that we have a dangerous version of spiky. Taking a consistent 60% of maximum DTPS may not be much better or worse than taking 70% some of the time and 50% the rest of the time, as long as healers are prepared for 70% throughput. It may be more convenient for them to heal, but I suspect that as long as the spikes remain below a comfortable threshold your healer won’t care that much. The “dangerous” spikes are the ones that exceed 80% throughput, which is why we look at them. We’ll get a better look at “dangerous spiky” when we look at some of the later queues.
Conclusions from Part 1
Going forward, it seems that the two queues we want to use as our gold standard are SB and SBr*. We could use SBr, but its more sensitive dependence on armor makes the results a little less general, and the TDR savings isn’t as significant. In addition, SBr and SB have very similar histogram distributions, while SBr* has a significantly different, flatter distribution. Since the results of SBr and SBr* are going to be similar, it may not matter too much, but it seems like a good idea to use the two that are most different just in case. So for each of the subsequent queue categories, I’ll include both of those columns on the table to make the comparisons easier.
In the next part of the series, we’ll start looking at more complicated queues that combine Shield Block and Shield Barrier. These will include several versions of the simple “bleed” queues that I used in the early analysis, as well as some more sophisticated queues that attempt to intelligently interleave Barriers and Blocks. While most of the work is already done, it’s fairly easy to add a queue type and model it, so feel free to suggest ways to perform that interleaving in the comments.