Kairos: rebuilding WoW rotation advice

By JDUpdated 9 min readDevelopmentGamingWorld of Warcraft
On this page
  1. A hidden stack count can still have readable consequences
  2. The signals Kairos uses
  3. Arcane Salvo is a useful test case
  4. Sometimes the missing value doesn't change the answer
  5. Running the priorities ourselves
  6. Testing the recommendation that was actually on screen

I'm building a WoW addon called Kairos to find out how much useful rotation advice we can recover from the combat information Midnight still lets addons read.

The goal is to run our own priority recommendations, using SimulationCraft's action priority lists and a model of what is happening to your character. Blizzard's Assisted Combat remains available as a fallback. It isn't the primary source of Kairos's recommendations on the specs we're developing.

That work is experimental. Arcane Sunfury and Demonology have independent implementations, and both still have gaps. The interesting part is how much of a decision we can reconstruct even when the game won't give us the underlying buff or cooldown directly.

A hidden stack count can still have readable consequences

Midnight's secret-value system allows addons to display some combat information while preventing their Lua code from inspecting it or using it in calculations. Blizzard describes that distinction in its explanation of the addon changes.

A priority list needs information it can reason about. If a rule says to cast one spell below a stack threshold and another above it, drawing the hidden stack count on screen doesn't give the addon a usable input.

But a hidden value can have observable causes and effects.

Your own successful cast may consume a buff. Another spell may build it. A readable activation glow may indicate that a threshold has been reached. A readable aura can give us a starting value before restrictions take effect, or correct the model when those restrictions lift.

Kairos combines those observations with explicit models of spell mechanics. Between reliable observations, the model estimates how the state changes. When a new observation establishes a value, it corrects the estimate.

The theory is that a priority list often needs less information than a complete reconstruction of combat. If the next decision only depends on whether you have at least 12 stacks, an estimate of 15 can produce the right recommendation when the real value is 17. An estimate of 11 when the real value is 12 can produce the wrong one.

That makes accuracy around the actual decision thresholds especially important.

The signals Kairos uses

The available information varies by spell, resource and game context. Kairos probes what the client actually exposes and checks reads for secret values before using them.

Signal What it contributes
Your own casts and readable cast/channel timing Previous actions, cooldown starts, resource changes and progress through a channel
Spell activation glows and readable spell replacements Proc presence, replacement-spell availability and some threshold confirmations
Readable resources Current Arcane Charges or Soul Shards, when those reads pass the checks
Readable aura, cooldown and charge data Starting values and corrections whenever the client exposes them
Cached talents, learned spells, gear and haste The applicable mechanics, available abilities and baseline timings
Movement and readable hostile nameplates Movement restrictions and an approximate enemy count

A glow needs to be interpreted carefully. It may establish that a proc exists without revealing its stack count. Several effects may also light the same spell. Nameplates give us an approximation of enemy count, with no guarantee that every counted enemy is in range of the recommended spell.

Even an aura lookup returning nothing needs care: some restricted lookups return nil for an aura that is present but secret. Kairos checks the restriction rather than treating that result as proof the buff is down. Leaving combat doesn't always make everything readable again, either; the raid probes found restrictions that persisted afterward.

The signal wrapper makes that boundary explicit. This is the read function from Core/Signal.lua; Known and Unknown construct tagged results for the state model:

function Signal.Read(fn, ...)
    if type(fn) ~= "function" then
        return Signal.Unknown("missing")
    end
    local ok, value = pcall(fn, ...)
    if not ok then return Signal.Unknown("error") end
    if issecretvalue(value) then
        return Signal.Unknown("secret")
    end
    return Signal.Known(value)
end

The wrapper checks whether the returned value is secret before handing it to the model. A failed or restricted read carries a reason instead of silently becoming zero or false. Tables whose individual fields can be secret have a separate field-reading guard.

Arcane Salvo is a useful test case

Arcane Salvo's stack count is hidden in combat in the tested builds. The Sunfury priority list uses several boundaries, including whether Salvo is below 12, above eight or exactly 25.

Kairos tracks Salvo from casts and channels. Arcane Blast and Prismatic Bolt add modeled gains. Missiles adds an estimated gain based on how much of the channel ran. A normal Barrage consumes Salvo and anchors the model at zero.

The Barrage glow provides another correction. Outside the competing effects that also light it, the glow can establish that Salvo has reached 25. Arcane Soul needs separate handling because it keeps Barrage glowing and changes Barrage's interaction with Salvo.

For an isolated Missiles channel, the basic estimate looks like this. The inputs below are ordinary numbers from the model and readable timing, with a positive channel duration. This shortened example leaves out new anchors, other casts and Arcane Soul handling:

-- Credit only the fraction of the channel that ran.
local fraction = math.max(0, math.min(1, elapsed / duration))
local gain = 11 * fraction  -- estimated full-channel gain
local estimate = math.min(25, startSalvo + gain)
local stacks = math.floor(estimate + 0.5)

-- One condition used by the Missiles priority rule:
local belowThreshold = stacks < 12

The 11-stack gain is the current model's average, not a guaranteed result of the spell. Starting from two stacks, a full channel gives an estimate of 13, so that below-12 condition is false. Other conditions still decide the recommended spell. If a normal Barrage consumes Salvo, the production model calls Salvo:Anchor(0); a qualifying glow can call Salvo:Anchor(25). Those observations replace the estimate before it keeps accumulating error.

Missiles is where the uncertainty becomes difficult. Its gains vary, and the tested visible spell fields haven't supplied a reliable way to identify Overpowered Missiles. The Missiles glow tracks Clearcasting; it doesn't distinguish that empowered channel.

That produces real errors near 12 stacks. A model can have a small average stack error and still choose the wrong spell when the true value sits just across the threshold. Repeatedly changing an average gain to improve the score on one recorded session would hide that problem. A new session has to test whether the model generalizes.

Cumulative Power is a more predictable example. Kairos models its buildup during Missiles, its consumption at eight stacks by Blast or Prismatic Bolt, and its expiration. In one later dummy session, the offline model answered the eight-stack condition correctly at 189 of 192 decisions; the live estimate did so at 187 of 194. Those results describe one buff condition in one session, rather than the accuracy of the entire rotation.

Sometimes the missing value doesn't change the answer

Demonology provides another approach to uncertainty.

A readable Demonbolt glow can establish that Demonic Core is present while its stack count remains hidden. When the priority evaluation needs that count, Kairos can try every possible positive value: one, two, three and four.

If every value produces the same recommended spell, the exact count isn't necessary for that decision. If the spell changes, Kairos falls back to Assisted Combat.

This shortened excerpt shows the check in DemonologyState:Pick. It runs only after the glow has established presence and the priority evaluation has read the unknown stack count. s is the non-secret model, pick is the initial recommendation, and evaluate() reruns the priorities while tracking unknown dependencies in reads:

local original = s.coreStacks
local sameSpell = true

for count = 1, 4 do
    s.coreStacks = count
    local other = evaluate()
    if (other and other.spellID) ~= (pick and pick.spellID) then
        sameSpell = false
    end
end
s.coreStacks = original

if sameSpell then reads.coreStacks = nil end
local uncertain = s.timingUnknown or next(reads) ~= nil

Only the Core-count dependency is cleared when all four answers agree. Unknown timing or another input can still force fallback. No hidden stack count is recovered or inspected: the engine tests whether that missing information can change this particular choice.

Other Demonology inputs need estimates. Wild Imps are modeled in batches from the player's own casts, with lifetimes based on their expected Firebolt expenditure and cached haste. The model pauses their lifetime clock outside combat after testing showed imps surviving between pulls.

It still misses imps idling without a target during combat, and Inner Demons' timer phase remains unmodeled. Those gaps can leave the estimated count too low and cost Implosion recommendations. They also show why a model that behaves well on a dummy needs dungeon testing.

Running the priorities ourselves

An offline translator converts pinned SimulationCraft action priority lists into Lua shipped with the addon. During play, the spec adapter supplies the reconstructed state and the generated rules evaluate it in priority order.

There is no live connection to SimulationCraft, and Kairos doesn't run a full simulation inside WoW. The translator supports part of the source lists; coverage reports identify unsupported rules and approximations. Some behavior also comes from explicit Kairos constraints, such as holding Arcane Surge until Touch of the Magi is ready.

For the normal main recommendation, the adapter projects deterministic effects to the end of the current cast or global cooldown. If you're already casting a resource generator, the next decision should account for the resources that cast is expected to provide. Projection uses a copy of state so those future changes don't affect the running model early.

New random procs aren't predicted. Ordinary additional queued recommendations remain withheld because the future proc state isn't modeled. Arcane's immediate Touch cue and Barrage/Touch pairing have separate timing handling.

Kairos's signal layer distinguishes known, estimated and unknown inputs. The current adapters accept modeled estimates as usable. Unknown reads along the evaluated priority path trigger fallback, including unknowns encountered in higher-priority rules that were rejected before the selected rule.

The fallback displays Blizzard's pick with an AC badge when that pick is available. It also supplies recommendations on specs without an independent Kairos implementation. An accepted estimate can still be wrong, though: fallback handles uncertainty the model recognizes, not every modeling error.

Testing the recommendation that was actually on screen

The live addon records its decisions and diagnostic inputs. After play, offline tools align that recorder with the combat log by matching casts, reconstruct state from the log, and evaluate the priorities against it. Kairos never reads the combat log live.

Timing matters here. By the time a cast event arrives, the display may already have advanced to a recommendation for the following global cooldown. Scoring that later icon against the earlier press would grade advice the player hadn't been shown for that decision. Recorder work now preserves evaluations from before the new GCD begins, with further live validation still needed.

The recorded results give us reasons to continue, along with specific problems to solve. In the Oct. 30 Arcane raid review, the displayed spell matched the reconstructed APL at 219 of 260 scored decisions across seven retained boss attempts, about 84%. Assisted Combat matched at 135 of 260, about 52%.

The Nov. 1 Demonology review recorded 88% agreement on a dummy pack and 80% in a Timewalking Everbloom run, compared with 75% and 64% for Assisted Combat in those sessions. Implosion accounted for most of Kairos's dungeon misses. These are local development-test results, with the capture and scoring limitations documented in the repo.

Agreement with the implemented priorities is what those percentages measure. They don't establish a DPS gain, complete SimulationCraft equivalence or readiness across every encounter. The replay reference also has approximations. The project's target of at least 90% main-icon agreement across a full Mythic+ key and a raid boss remains unmet.

Tuono's reconstruction research was a reference for this approach; Kairos is a separate implementation. The current addon runs entirely in Lua, uses non-secret inputs for its decisions and leaves every spell press to the player.

The Kairos repository contains the models, generated priorities, probe findings and replay reports. The next work is to test the recorder changes in new sessions, investigate whether any readable Implosion signal helps with imp counts, and separate Arcane's remaining threshold errors from projection and timing errors.

Comments

Sign in or create an account to comment and vote.

No comments yet. Start the discussion.