-
Notifications
You must be signed in to change notification settings - Fork 12
Documentation: determine_stats() method
Shadowcraft has a dynamically iterative system to determine actions per second. Called the determine_stats() method, it sorts procs, determines stats, and runs the aps methods (aps methods being actions-per-second, spec specific modeling).
It starts off by generating the base stats used by the algorithms. current_stats takes the amount of several stats from gear, buffs, racials and whatever else may constantly be active. This value is multiplied by a stat modifier, which can be modified by things like attunements, spec traits, and ring procs.
The method then proceeds to sort through several types of procs. Each proc is given a specific bucket, or type. These include RPPM, RPPM_stat_mod, ICD, NO_ICD, damage_procs, and weapon_damage_procs. Each proc needs the distinction due to how they're sorted.
RPPM procs have a constant uptime, and can be handled right after checking for stat modifications. Stat modifications need to be handled first to prevent things from not applying to all RPPM agi procs. RPPM_stat_mod bucket exists solely for the new Warlords rings that grant bonus agi at the moment this is written. As you hopefully noticed, this means determine_stats() generates base stats before handling RPPM_stat_mod procs, so while iterating through these stats and determining their significance, each stat_mod that gets updated also updates the base stats.
After handling the first bucket, the actual RPPM procs are handled similarly, but dumped into a static_stats dict instead. This dict serves as a cache for later on in the method. The static_stats are then immediately dumped into the current stats object, which lets us calculate the first APS call to get a baseline performance for the next type of proc to be dealt with.
The next thing that comes up is a somewhat intelligent system for determining if the current profile needs to calculate to convergence. Each spec can specify when it forces convergence at any point while setting up the data, or during APS calls. Combat, for instance only forces convergence while working with Death From Above. Sub always forces convergence in the profile setup phase. The code block also knows to check for specific types of procs that would require convergence regardless of their spec script requirements. These procs were sorted into the NO_ICD bucket. This type of proc is a stat proc that can alter its uptime based on the rate of actions made, or other stats procing with it. Some stats will do this to one spec, but not another. If any of the above conditions are met, ShC will start to iterate until it converges (see while (need_converge or self.spec_needs_converge):) or the script determines it's not going to have to bother anymore.
While converging, the while block needs to recalculate what stats are currently available at that point in time. This is why that RPPM cache from earlier was necessary. It reduces method calls for performance's sake. So you can see the while block start off with the familiar current_stat creation, but when you get to the NO_ICD bucket, you should notice a particular new line that checks if a proc contains a stat that a spec says it needs convergence to handle. This is part 2 of the somewhat intelligent convergence checking. Some specs like Combat need to handle convergence if a stat like haste or mastery is ever included. crit/versatility/multistrike however, will not create a ripple effect and will not need to be recalculated to convergence to find a value. If the code doesn't need to converge anymore, it just breaks out of the while loop to handle remaining tasks. This is seen in both the stat check but also the are_close_enough() call which checks if two provided aps dicts are essentially the same.
After this, it's much of the same thing. The ICD proc bucket is handled like the previous buckets were, the current_stats is no longer recalculated, and damage procs like Shattered Hand, etc, are done at the end.