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'] * 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) 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) 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.
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.