To understand the intentions of this thread, please read this thread.
This thread was last updated on 2015-10-19, based on the Arcane Mage APL from SimulationCraft 6.2.0 Release 3 (Developer), Commit Hash: b72801540019cc4f3a3e97ba1c7110e381510953
Overview
The primary strength of Arcane has always been single target damage, and as such, most of the optimization efforts have been put into making sure single target performs well. In other words, not as much effort has been put into AOE and cleave.
At a meta level, the name of the game for Arcane APL optimization is "usage". Maximizing usage of high priority spells while minimizing downtime/low-throughput-time is the number 1 factor. It is far more important than little tricks such as gaming Incanter's Flow or trinket procs with Supernova or Arcane Missiles. Here are some goals that consistently correlate with throughput:
- Maximize uptime/usage of cooldowns (Arcane Power/Prismatic Crystal) and talents (Supernova/Nether Tempest/Rune of Power/Mirror Images)
- Avoid capping mana, and get as close to converting 100% of excess mana into Arcane Blasts and Missiles casted at maximum Arcane Charges
- Stay as close to full mana as possible otherwise (while avoiding mana cap), to maximize benefits from Mana Adept
Under SimulationCraft, many of these metrics are easily trackable. For the default 450(+-20%) second simulation, we can expect a theoretical maximum of <=20.0 Supernova casts on average, <=5.0 Prismatic Crystal casts and unglyphed Arcane Power casts (3.0 glyphed) and so on. Nether Tempest and Rune of Power should come very close to 100% uptime. Recorded excess mana overflow should be as close to <0.2% as possible; an unavoidable amount of loss happens on the pull and due to Evocations. The mana level graph, which is averaged across all iterations, should be well above 93% at any point not indicative of a burn phase. These numbers are early health indicators for any APL change, telling us if Robomage is missing usages on high priority spells due to errors in the APL.
The Arcane APL is structured into a number of separate subaction-lists, which indicate the actions an Arcane Mage uses in different conditions. It is important to note that APLs are by default, stateless. That means without extra help, Robomage does not have "modes", nor does he remember which subaction-list he was previously using an action from. Each time Robomage chooses an action, he always starts from the very top of the master list, and then traverses through all individual lists.
Arcane APL is structured into a number of separate subaction-lists, which correspond the actions an Arcane Mage uses in specific scenarios. It is important to note that SimulationCraft APL by default is stateless. That means that without external help, Robomage does not have "modes", nor does he remember which subaction-list he was previously using, nor does he consult previous actions performed by default. Choosing an action always starts at the very top of the master list, and then traverses through individual lists to search for suitable candidates. As it turns out, this has important consequences on how the Arcane APL burn phase is implemented.
With all that out of the way, let's dive into the actual APL of Arcane.
Master List
The master list within the Arcane APL consists of 12 lines. I have bolded the ones with significant DPS performance impact, which I will elaborate on. The others are still significant, but involve complicated simulation mechanics (enemy casting, raid-wide AOE damage, raid-wide Bloodlust override flags, movement events) that are beyond the scope of this thread and cannot be covered in detail.
actions=counterspell,if=target.debuff.casting.react
actions+=/stop_burn_phase,if=prev_gcd.evocation&burn_phase_duration>gcd.max
actions+=/cold_snap,if=health.pct<30
actions+=/time_warp,if=target.health.pct<25|time>5
actions+=/call_action_list,name=movement,if=raid_event.movement.exists
actions+=/rune_of_power,if=buff.rune_of_power.remains<2*spell_haste
actions+=/mirror_image
actions+=/cold_snap,if=buff.presence_of_mind.down&cooldown.presence_of_mind.remains>75
actions+=/call_action_list,name=aoe,if=active_enemies>=5
actions+=/call_action_list,name=init_burn,if=!burn_phase
actions+=/call_action_list,name=burn,if=burn_phase
actions+=/call_action_list,name=conserve
At the top of list we see a weirdly named action: stop_burn_phase. As the name suggests, this is a custom made action that indicates the the mage the end of a burn phase, and the line is skipped when a burn phase is not currently under way. To avoid confusing you with a longwinded sidetrack right now, I will leave the full explanation of this system later. However, it is important to note the position of the line near the top of the list; It is there for a reason.actions+=/stop_burn_phase,if=prev_gcd.evocation&burn_phase_duration>gcd.max
Following that we have usage of Rune of Power and Mirror Image. They are only placed this highly to ensure they have near maximum uptime. The condition "rune of power remaining duration < 2 * spell_haste seconds" has a small catch to it; spell_haste as a SimulationCraft variable is a misnomer, and in fact indicates spell_speed. This is the ratio of a spell's cast time to its base cast time, ie. cast_time = spell_speed * base_cast_time.actions+=/rune_of_power,if=buff.rune_of_power.remains<2*spell_haste
actions+=/mirror_image
The value "2 * spell_haste" is in fact the channel duration of Arcane Missiles, which is the longest duration cast for Arcane Mages aside from Arcane Blasts casted at low Arcane Charges. Therefore, this statement can actually be understood as "recast RoP if an important cast time spell cannot be fully benefit from it". The reason that we must hardcode 2.0 seconds instead of accessing Arcane Missile's channel duration directly, is because SimualtionCraft regards all channeled attacks as DOTs that prevent casting, and therefore Arcane Missiles has an official "cast time" of 0 seconds. As you'll soon notice, this pattern will appear multiple times within the Arcane APL.
Some of you might be wondering what the line is doing here. You've probably forgotten that Cold Snap has the side effect of also resetting the CD on PoM. By using Cold Snap as soon as Presence of Mind is consumed (note: you need to cast a spell to consume it AFTER using PoM!), you are able to gain an extra charge of PoM every 3 minutes.actions+=/cold_snap,if=buff.presence_of_mind.down&cooldown.presence_of_mind.remains>75
Here we have the 4 core sections of Arcane's execution listed in order:actions+=/call_action_list,name=aoe,if=active_enemies>=5
actions+=/call_action_list,name=init_burn,if=!burn_phase
actions+=/call_action_list,name=burn,if=burn_phase
actions+=/call_action_list,name=conserve
1. If you have lots of enemies, prioritize AOE spells.
2. If you're not in burn phase, check if you should be preparing for a burn phase
3. If you are current in a burn phase, execute an action from the burn phase subaction-list
4. Otherwise, execute conserve phase actions.
The ordering of this list has an unfortunate consequence: during heavy cleave fights, Robomage will give up single target boss damage, and use AOE spells to pad the meters instead. Often, this is not what we do in raids. However, this design is a necessary evil. The alternative would have been Robomage merrily spamming his Arcane Blasts into murloc #151 out of 200, even if he isn't talented for Unstable Magic. AOE simulations would be a complete disaster.
Burn Phase Implementation
As mentioned in the overview, SimulationCraft action list traversal by the actor is by default, stateless. Without custom tools, there is no simple way to force Robomage into a subset of actions at specific times. This creates issues for implementing the burn phase. Say we decided to implement burn phases as follow:
Such a list would have the following happen: Robomage will spam a bunch of Arcane Blasts, suddenly realize that his poor Arcane Missiles RNG means his mana won't last until Evocation is back, and therefore skip the do_burn_phase_stuff line and opt to do_conserve_phase_stuff instead - most likely using Arcane Barrage to drop Arcane Charges at 60% mana. Then, he will realize that because Evocation is almost up, he should go back to burning again, and waste his time building 4 Arcane Charges again before restarting his burn and finally Evocating. For any reader that has reached this point in the thread, I shouldn't have to explain to you why interrupting your burn with an Arcane Barrage is a terrible idea.actions+=/do_burn_phase_stuff,if=mana_will_last_until_evocation_is_back
actions+=/do_conserve_phase_stuff
The solution we opted for, was to introduce 2 custom actions and a boolean flag: start_burn_phase, stop_burn_phase, and burn_phase. Simply put, start_burn_phase sets the value of burn_phase to true, while stop_burn_phase sets it to false. There is also a custom expression called burn_phase_duration that returns the amount of time spent in a burn phase.
With these custom actions and flags, we can now control burn phase logic by toggling burn_phase to true and false, and use it to drive Robomage into burn phases and conserve phases. After start_burn_phase, Robomage will stubbornly execute burn_phase gated actions regardless of mana, until we tell it to stop_burn_phase. Right now, burn phases are ended with the execution of Evocation. By putting stop_burn_phase at the top of the master list and setting a condition of "if=prev_gcd.evocation&burn_phase_duration>gcd.max", Robomage is automatically returned to conserve phase each time Evocation is casted as the previous action.
So what is burn_phase_duration>gcd.max doing there? The burn_phase_duration check is meant as a safeguard against an infinite loop. Near the end of a fight, Robomage will attempt to start_burn_phase to burn mana before the boss is dead. Sometimes, this coincides with the time when Robomage has just ended a burn phase. However, since the previous casted spell is still Evocation, this ends in Robomage infinitely calling start_burn_phase and stop_burn_phase, resulting in a SimulationCraft crash. By requiring a burn phase to have a non-zero duration before calling stop_burn_phase, we avoid this problem.