The Unofficial ShadowCraft Documentation-Resource Budget Modeling

One of the most basic questions when writing a model is what is the model actually modeling? Those of you who have been around the rogue community for a while will remember the Wrath (and earlier) notation of “3s/5r/5e” corresponding to 3 cp slice and dice, 5 cp rutpute, 5 cp eviscerate. This fixed rotation is clearly an abstraction however it exposes a useful concept which is still used in ShadowCraft today, resource budget modeling (my term).

There are three basic ideas of resource budget modeling:
1) Rogue ability selection can be defined in terms of finisher usage.
2) Certain finishers must be kept up 100% of the time and can be considered a fixed cost.
3) The duration of one of these 100% uptime finishers defines a time slice or cycle.

Bringing these three ideas together we have a pool of resources per cycle. Since we are being finisher centric the next step is to define the energy cost of one finisher, that is the energy cost of both the combo points and the finisher. This can actually be a bit more complex than it seems as I’ll discuss in a later post but the simple solution is reasonably apparent. Simply divide the target number of combo points per generator by the total combo points for the finisher noting that the total number of combo points per generator includes things like blindside procs.

Now we have energy costs per finisher and we have a resource pool of energy per cycle. By assuming 100% uptime on certain finishers those finishers can be considered a fixed cost in our resource pool and whatever is left over can go into the “spammable” finisher, eviscerate or envenom. With numbers of each finisher per cycle, a cycle duration and combo generators per cycle we can generate attacks per second for each ability. There are some complexities the above discussion ignores, honor among thieves combo points as subtlety and revealing strike rather than sinister strike usage as combat but both of those can be handled using basically the same approach.

Now some code. Since this is the first post in this series a quick word on how ShadowCraft is organized. The major theorycrafting function in ShadowCraft is the attack_counts function for each spec. These functions drive ShadowCraft generating a list of attack counts per second for each ability that is then used to do the actual damage computation.

For this code example we’re going look at the assassination anticipation attack counts function because it is the simplest. From line 1648 in ShadowCraft. From here on the line numbers will refer to line numbers in the code block below.


cp_per_finisher = 5
avg_rupture_length = 4. * (6 + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) # 1+5 since all 5CP ruptures
avg_wait_to_strike_connect = 1 / self.geometric_strike_chance - 1
avg_gap = 0 + .5 * (avg_wait_to_strike_connect + .5 * self.settings.response_time)
avg_cycle_length = avg_gap + avg_rupture_length
attacks_per_second['rupture'] = 1 / avg_cycle_length
rupture_ticks_per_second = 2 * (6 + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) / avg_cycle_length # 1+5 since all 5CP ruptures
attacks_per_second['rupture_ticks'] = [0, 0, 0, 0, 0, rupture_ticks_per_second]

energy_regen_with_rupture = energy_regen + attacks_per_second['rupture_ticks'][5] * vw_energy_per_bleed_tick
energy_per_cycle = avg_rupture_length * energy_regen_with_rupture + avg_gap * energy_regen
cpg_per_finisher = cp_per_finisher / avg_cp_per_cpg

energy_for_rupture = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('rupture', hit_chance=self.geometric_strike_chance, cost_mod=ability_cost_modifier)[0]
energy_for_rupture -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp
energy_for_envenoms = energy_per_cycle - energy_for_rupture

envenom_energy_cost = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('envenom', hit_chance=self.geometric_strike_chance, cost_mod=ability_cost_modifier)[0]
envenom_energy_cost -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp
envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost

envenoms_per_second = envenoms_per_cycle / avg_cycle_length
cpgs_per_second = envenoms_per_second * cpg_per_finisher + attacks_per_second['rupture'] * cpg_per_finisher

Since slice and dice is refreshed by envenom we use rupture to determine cycle length. Lines 1 through 8 above determine the length of the cycle. Line 2 handles the length of rupture so it checks for the existence of T15 2pc. Lines 3 and 4 handle the additional time caused by miss rates and player reaction time.

Lines 10 through 12 determine the resource pool, note that line 11 makes a distinction between the portion of the cycle with rupture up and the portion without rupture up.

Lines 14 through 20 determine the total costs of each finisher. Lines 14 and 18 account for the additional cost added by misses and lines 15 and 19 handle relentless strikes. Line 16 computes the remaining energy available for envenom and line 20 uses that number to determine how many envenoms are used per cycle.

Finally lines 22 and 23 convert those envenoms per cycle into envenoms and combo generators per second.

Beyond this the computed values are written into the attacks_per_second list for damage calculations. I did not show the computation of combo points per combo generator but that can be found above the discussed section.

If you would like to look at how other spec’s resource budgeting works you can look at sections starting at line 1522 (assassination no anticipation), 1919 (combat) and 2225 (subtlety).

Since this is the first post in what I hope will be a long running series I’m still trying to work out some of the formatting. Any feedback on where things are unclear would be welcome. ShadowCraft is also a big topic so if there is some part of it you are especially curious about let me know in the comments on twitter.

The Unofficial ShadowCraft Documentation-Introduction

In my very first post on this blog I talked about the difference between simulations and models. Simulations most people get, they correspond pretty closely to how most people think about theorycrafting and testing. Run a test of some behavior and look at the results. Models are a bit trickier because models rely on simplifying assumptions which can often be somewhat obtuse. The goal of this series of posts is to to explain those simplifying assumptions and show examples of them within the ShadowCraft codebase.

Another goal of this series is to get more people looking at and contributing to the ShadowCraft codebase. I know from experience the ShadowCraft codebase can be very intimidating to start working through partially because there isn’t an obvious starting point. By showing and explaining pieces of the ShadowCraft model I hope to make the prospect of digging into ShadowCraft a bit easier.

ShadowCraft is written entirely in Python, for the purposes of this series I am going to assume the reader has a decent understanding of Python. There are some pieces of code which are a bit obtuse and I there I will explain the syntax to an extent. For people who don’t know Python I will still explain the underlying concepts in plain English (and possibly a little math). If you do want to learn Python I recommend the free book A Byte of Python. Python is one of the easier programming languages to learn and is about as close to plain English as a programming language can get. For those with experience with C style languages (Java, C, C++, C#, etc.) a lot of Python syntax should look very familiar.

A final caveat, ShadowCraft is an actively developed project so the code samples I use may not be what ShadowCraft actually uses at the time you are reading this however the focus of this series are the underlying modeling techniques which should still remain broadly applicable even if the code changes.