Difficulty is two dials
Every spawn table in the game is indexed by two numbers, challenge and resistance. Havoc ranks just pick which row you get, and rank 30 reaches a row normal Auric never touches.
The mission board shows one difficulty, but the director reads two numbers:
- Challenge (C) picks rows in tension, ramp-up, horde composition, monster count, melee aggression and disabler caps.
- Resistance (R) picks rows in the specials, horde-timer and patrol-density tables.
A lookup is clamped to the table length, so asking for row 6 of a five-row table returns row 5.
Tables are indexed by challenge or resistancemanagers/difficulty/difficulty_manager.lua:62
DifficultyManager.get_table_entry_by_challenge = function (self, t)
local challenge = self._challenge
return t[math.min(#t, challenge)]
end
DifficultyManager.get_challenge = function (self)
return self._challenge
end
DifficultyManager.get_resistance = function (self)
return self._resistanceOpen managers/difficulty/difficulty_manager.lua at line 62 on GitHubWhat each Havoc rank maps to
| Havoc rank | Challenge | Resistance | Same as |
|---|---|---|---|
| 1-10 | 3 | 3 | Malice |
| 11-20 | 4 | 4 | Heresy |
| 21-29 | 5 | 4 | Damnation |
| 30 | 5 | 5 | Auric |
| 31-40 | 5 | 6 | nothing outside Havoc |
The bracket code only goes up to R5. Ranks 16-29 add the mutator_increased_difficulty circumstance and ranks 30+ add mutator_highest_difficulty. The second one contains a mutator that adds one more point of resistance. That is why rank 30 plays at R5 and ranks 31-40 play at R6, a column that many specials, horde and patrol tables have but normal play can never reach.
Havoc rank to challenge / resistanceutilities/havoc.lua:118
if havoc_rank <= 10 then
difficulty.challenge = 3
difficulty.resistance = 3
elseif havoc_rank >= 11 and havoc_rank <= 20 then
difficulty.challenge = 4
difficulty.resistance = 4
elseif havoc_rank >= 21 and havoc_rank <= 30 then
difficulty.challenge = 5
difficulty.resistance = 4
else
difficulty.challenge = 5
difficulty.resistance = 5
endOpen utilities/havoc.lua at line 118 on GitHubRank 16+ and 30+ difficulty circumstancesutilities/havoc.lua:89
increased_difficulty = "mutator_increased_difficulty"
chosen_circumstances = chosen_circumstances .. ":" .. increased_difficulty
elseif havoc_rank >= 30 then
increased_difficulty = "mutator_highest_difficulty"
chosen_circumstances = chosen_circumstances .. ":" .. increased_difficulty
end
local chosen_modifiers = ""
local current_modifer_level = HavocModifierConfig[havoc_rank]
local index = 1Open utilities/havoc.lua at line 89 on GitHubRank 30+: +1 resistancesettings/mutator/templates/mutator_modify_pacing_templates.lua:250
mutator_add_resistance = {
class = "scripts/managers/mutator/mutators/mutator_modify_pacing",
init_modify_pacing = {
modify_resistance = 1,
},
},Open settings/mutator/templates/mutator_modify_pacing_templates.lua at line 250 on GitHubRank 30 is a real wall, not a cosmetic step. The specials table jumps from 6 to 8 alive, timers drop from 120-320 to 80-240, the coordinated strike chance doubles to 50%, and quiet phases disappear (see the next chapter). Treat 29 to 30 like moving up a whole difficulty.
On top of these dials, every rank adds a stack of modifiers: tougher elites, more specials alive, faster hordes and so on. The rank calculator shows the exact set for any rank.