← Core Game Mechanics Enemy Aggro System Open Calculator →

Enemy Aggro System

Authors:perturbperturbIncognitoIncognito

Summary

This article covers how enemies decide who to focus on; how they discover players, pick a target, and change targets when someone starts shooting them. It does NOT cover when enemies actually get to attack; that's the Combat Token System, which governs attack pacing and turn-taking.

Every enemy stores its current target in the Enemy blackboard key. All behavior tree distance checks, attack targeting, and positioning decisions reference this key. If you are not an enemy's Enemy, your position is irrelevant to its AI — you could be standing on its head and it won't care unless you're its target.

The system operates in three layers, in order:

  1. Detection — How enemies discover players exist (vision, hearing, damage, chain alerts). An enemy can't target what it doesn't know about.

  2. Enemy Selection — From known targets, who does the enemy focus on? A utility-based scoring system with decision stickiness that re-evaluates every second.

  3. Threat Knowledge — Incoming damage can override the selection system, forcing target swaps via RangeThreat/MeleeThreat interrupts. This is the "aggro stealing" layer — and the part players have the most direct control over.


Detection

Before an enemy can target a player, it must first detect them. The Advanced Enemy Selection pipeline feeds from a set of Sensors and Observers that populate the enemy's known-targets list. An enemy that hasn't detected you will not target you, regardless of distance or damage.

Detection Sensors

The pipeline includes 6 sensor types, defined in {SERVER}/ssl/ai/enemyselection/advancedenemyselection/sensorsobservers/:

Sensor

How It Works

Vision

Line-of-sight detection within a per-enemy aggro radius. Range varies enormously: Spore Mine = 20m, Tyranid Warrior Sniper = 115m, Zoanthrope = 140m. Requires unobstructed sightline

Hearing

Sound-based detection. Gunfire, abilities, and movement generate sound that can alert enemies outside visual range

Damage

Taking damage from a previously unknown actor instantly reveals them. You always become known to an enemy you shoot, even outside its vision range

AI Target

Script-assigned targets from mission events. The game director can force an enemy to know about specific players (e.g., boss encounters, scripted ambushes)

Boss Sixth Sense

Bosses can detect players regardless of line of sight. Prevents players from "hiding" from boss enemies

Player Got Detected

Chain detection — when one enemy spots a player, nearby allies become aware too. This is how an entire group turns on you after one enemy sees you

Detection Propagation

Once an enemy detects a player, it can spread awareness to allies through two mechanisms:

Aggro Scream — an active alert broadcast:

  • Scream Delay: 0.6 seconds after detection

  • Scream Duration: 2 seconds

  • Cooldown: 20 seconds

When an enemy spots a player, it waits 0.6s then "screams" for 2s, alerting nearby allies. The 20s cooldown prevents constant re-broadcasting. This is the primary chain-detection mechanism — one Termagant spots you, screams, and the whole group turns.

Aggro Brattle — passive ambient awareness:

  • Update Period: 1 second

A continuous background check that propagates awareness periodically. Less dramatic than scream but ensures nearby enemies eventually learn about detected players even without an active alert.

Per-Enemy Detection Ranges

From individual enemy writeups, confirmed vision/aggro detection ranges:

Enemy

Detection Range

Notes

Spore Mine

20m

Speeds up from 4.5 to 6.5 m/s on alert

Acid Mine

20m

Same as Spore Mine

Tyranid Warrior (Sniper)

115m

Huge detection range matches sniper role

Zoanthrope

140m

Largest confirmed detection range

Chaos Cultist

Squad-based

25m squad formation search radius

Most enemies use default detection ranges that we haven't individually confirmed — the values above are the ones explicitly documented in enemy writeups.

Detection Implications

  • You can avoid aggro entirely by staying outside detection range — but the Damage sensor means any shot you take immediately reveals you

  • Chain detection means stealth is fragile — one enemy's scream alerts the group within 0.6s

  • Bosses can't be hidden from — the Sixth Sense sensor bypasses all stealth

  • Detection is prerequisite, not targeting — being detected adds you to the known-targets pool, but Enemy Selection (Layer 2) still decides who the enemy actually focuses on


Enemy Selection

Once detected, potential targets enter the Advanced Enemy Selection pipeline — a utility-based system that scores each known target and picks the highest. The Enemy blackboard key is set to the winner.

Selection Pipeline Components

The pipeline runs through these controllers:

Controller

Role

Selection

Runs every 1 second; evaluates all potential targets (maxActors = -1, unlimited)

Known Enemies

Maintains list of actors the enemy is aware of (fed by Layer 1 sensors)

Exposed Enemies

Tracks which actors are currently visible/exposed

Vision History

Remembers previously seen actors (persistence after breaking line of sight)

Aggro Registrator

Registers aggro state changes

Blackboard Updater

Pushes selection result into the Enemy blackboard key

Domain Restriction

Constrains selection to valid domain (prevents targeting across arenas, etc.)

Raw Damage Received

Tracks damage from each actor; decay rate = 10

Default Target Scoring

The default enemy selection utility (utilityfunctiondefaultenemyselection.sso) scores each potential target as:

Score = CanBeAiTarget × min(DecisionStickiness, Priority) × TargetDesirability

Where Priority is a weighted average of:

  • Base Aggressiveness (weight: 10) — primary factor, based on the UtilityFunctionAggressiveness subfunction

  • Player Contribution (weight: 1) — minor factor from UtilityFunctionEnemySelectionPlayerContribution

The heavy 10:1 weight toward aggressiveness means the base scoring dominates over player contribution tracking.

Decision Stickiness

Enemies don't instantly swap to a new target every cycle. The UtilityFunctionDecisionStickiness module provides a bias toward keeping the current target:

  • Stickiness Duration: 7 seconds — after selecting a target, that target gets a bonus score for 7s

  • Stickiness Fade Duration: 3 seconds — the bonus fades over 3s after the 7s expires

This means an enemy typically holds its current target for ~7-10 seconds before being open to swapping, unless a threat module (Layer 3) forces an override.

Melee Enemy Selection

Melee-specialized enemies use utilityfunctionmeleeenemyselection.sso, which adds:

  • Path validation — target must have a valid navigation path (unreachable targets score 0)

  • Melee Aggressiveness override — replaces base aggressiveness with UtilityFunctionAggressivenessMelee

Power-Level-Based Selection

Some enemies use utilityfunctionenemyselectionbypowerlevel.sso, which factors in:

  • Target power level — spline from 0→0.1 to 100→1.0, meaning higher-power targets are preferred

  • Distance decay — "Decay Closer" spline from 0→0.1 to 80→1.0 (closer = lower score, favoring distant targets)

  • Decision stickiness — 7s duration, 3s fade (same as default)

  • Player contribution — inverted (NOT player contribution), meaning enemies using this profile are LESS likely to target the player who's been contributing most


Threat Knowledge

This is the "aggro stealing" layer. While Layer 2 runs its selection cycle every second, Layer 3 can interrupt that process — incoming damage sets blackboard keys (RangeThreat, MeleeThreat) that force target swaps mid-cycle.

Every enemy includes one or more AI Knowledge Modules that track incoming damage and fire when thresholds are exceeded. These are the core defaults — individual enemies can (and frequently do) override every parameter.

Default RangeThreat

Parameter

Value

Blackboard Key

RangeThreat

Time Window

2 seconds

Damage Threshold

2

Distance

0–30m

Damage Types

bullet, heavybullet, shotgunbullet, explosivebullet, chargedplasma, plasma, melta, laser, gunstrike, volkite, volkiteexp

Filter

DamageFromPc (player-controlled actors only)

When an enemy takes 2+ qualifying damage within 2 seconds from a player within 30m, the RangeThreat blackboard key is set to that player. This is the primary mechanism for "stealing aggro" with ranged fire.

Key exclusions from the default filter: Melee damage, fire/burn damage, generic explosive damage, and player perk damage (dmgtypeplayer_perk) do NOT trigger RangeThreat by default. Shooting is the intended trigger.

Default RangeFarThreat

Parameter

Value

Blackboard Key

RangeFarThreat

Time Window

2 seconds

Damage Threshold

4

Distance

30–50m

A separate module for players engaging from farther away. Higher threshold (4 vs 2) — you need to deal more sustained damage from range to register as a threat. No damage type filter is specified (only distance filtering), which means it may accept all damage types at this range.

Default MeleeThreat

Parameter

Value

Blackboard Key

MeleeThreat

Time Window

1.5 seconds

Damage Threshold

6

Update Period

0.1 seconds

Damage Types

melee, melee_push, contact

Melee threat requires substantially more damage (6 vs 2 for ranged) in a shorter window (1.5s vs 2s). This makes ranged fire significantly more effective at pulling aggro than melee attacks. The high threshold means weak melee hits won't trigger a target swap.

TopDamageThreat

Parameter

Value

Update Period

0.2 seconds

Time Window

1 second

Filters

Targetable actors only, ignores friendly fire

A global module that tracks who is dealing the most damage overall within a 1-second rolling window. Used by some enemy selection utility functions as a factor in target prioritization (not directly tied to a blackboard key like the others).

Per-Enemy Overrides

Most enemies override the default thresholds. Here's a compiled reference of every enemy whose threat parameters we've confirmed differ from defaults:

Enemy

Type

Threshold

Window

Distance

Notes

Biovore

Range

20

5s

Extremely high threshold; hard to pull aggro with chip damage

Helbrute

Range

15

6s

Also requires first event >=3s old, last <=2s old; ignores current target

Helbrute

Melee

5s

First event >=3s old, last <=1s old; ignores current target

Rubric Marine (Flamer)

Range

15

2s

40m max

High threshold for a common enemy

Rubric Marine (Flamer)

Melee

10

1.8s

Rubric Marine (Bolter)

Range

Default

1.3s

Unlimited

Shorter window than default

Tzaangor Spearman

Range

10 raw

1.8s

Unlimited

Uses raw (pre-mitigation) damage

Tzaangor Spearman

Melee

5

1.8s

Tzaangor

Range

3 raw

Default

Very sensitive (default module)

Tzaangor

Melee

1 raw

3s

Extremely sensitive — any melee hit triggers

Tyranid Warrior (Sniper)

Range

8

Default

110m max

High threshold but huge detection range

Tyranid Warrior (Sniper)

Melee

7

2s

Tyranid Warrior (Whip)

Range

5

1.5s

150m max

Tyranid Warrior (Whip)

Melee

4 raw

1.5s

Uses raw damage

Zoanthrope

Range

3

1.5s

Only filtered types (bullet, plasma, etc. — NOT melee, fire, explosive)

Neurothrope

Range

3

1.5s

Same as Zoanthrope

Lesser Sorcerer

Range

None

No RangeThreat module at all; melee-only recovery

Lesser Sorcerer

Melee

9

1.5s

Ravener

Melee

1

3s

useRelativeValues = False; any hit triggers

Hormagaunt

No recovery system at all

Termagant

No recovery system; uses IsScared flee behavior instead

Chaos Cultist

No recovery; squad suicide/panic instead

"Raw" vs "Modified" damage: Some enemies check checkRawDamage = true, meaning pre-mitigation damage counts. Others check post-mitigation. This matters when enemies have damage sensitivity states active — raw-checking enemies are easier to trigger threat on while they're in damage reduction states.


Threat Density

The floatvalueproviderthreatdensity.sso assigns threat weights by enemy classification. This is used by the AI to evaluate how dangerous a group of enemies is in an area:

Classification

Weight

FODDER

0.2

COMMON

0.25

ELITE

0.5

SPECIAL

1.0

MINIBOSS

1.0

BOSS

1.0

Fodder enemies (Hormagaunts, Cultists) contribute very little to threat density. Elites (Terminators, Warriors) contribute half. Specials, Minibosses, and Bosses all have equal maximum weight.


Recovery

When a threat module sets RangeThreat or MeleeThreat, the enemy's behavior tree enters its Recovery subtree. Recovery is an interrupt system; it can pause the enemy's current action (if AllowInterruptions is true) and execute a response.

Recovery responses vary wildly by enemy, but common patterns include:

Response Type

Examples

Effect on Aggro

Dodge + Counter

Neurothrope, Tyranid Warriors

Evades then retaliates; often sets threat source as new Enemy

Shield/Stance Swap

Zoanthrope (Bolster Defense)

Changes defensive posture; 7.7s cooldown between swaps

Teleport

Helbrute (Phase 2), Lesser Sorcerer

Repositions to threat source or away from it

Block + Counter

Tzaangor (shield block), Lesser Sorcerer

Absorbs hit then counterattacks; can set attacker as new target

Reposition

Biovore (unanchor), Rubric Marines

Moves to new position; target swap depends on implementation

Escalation

Hierophant (cooldown reduction)

Becomes MORE dangerous; reduces shooting cooldown by 15s

Flee/Panic

Termagant (IsScared), Chaos Cultist (suicide)

Disengages combat entirely

Critical insight: Recovery often makes enemies MORE dangerous, not less. The Helbrute's ranged threat response reduces its shooting cooldown. The Hierophant gets faster. Triggering recovery carelessly can escalate the fight.


Player Manipulation

Avoiding Detection

  • Stay outside an enemy's vision radius to avoid being added to its known-targets list

  • But the Damage sensor means any shot reveals you instantly — there's no stealth sniping

  • Chain detection (Player Got Detected sensor + Aggro Scream) means one alert spreads to the group in ~0.6s

  • Bosses have Sixth Sense — they can't be hidden from at all

Stealing Aggro (Layer 3 Overrides)

  1. Ranged fire is king — RangeThreat has a lower threshold (2 damage) than MeleeThreat (6 damage). A few shots steal aggro faster than sustained melee

  2. Stay within 30m for default RangeThreat. Beyond 30m you hit the RangeFarThreat module which requires double the damage (4 threshold)

  3. Use the right damage types — bullet, plasma, melta, laser, volkite all work. Fire and generic explosive do NOT trigger RangeThreat by default

  4. Enemies hold targets for ~7-10 seconds (decision stickiness). Brief damage won't instantly pull an enemy off a teammate — you need to sustain pressure or deal enough to force a threat module override

Off-Screen Safety

The frustum aggro penalty (0.6x for enemies behind you) means enemies you can't see are 40% less likely to get attack tokens. See the Combat Token System for the full scoring breakdown and how forced tokens bypass this entirely.

High-Value Target Enemies

Some enemies using power-level-based selection actively prefer higher-power targets. Players running high power-level loadouts will attract more aggro from these enemies.

Per-Enemy Threat Exploitation

  • Biovore (20 dmg/5s): Extremely hard to pull aggro with ranged fire alone. Close to 25m and stay there for 2s to force unanchor instead

  • Helbrute (15 dmg/6s with age requirement): The first-event-age requirement (>=3s old) means burst damage won't trigger threat — you need sustained fire over 3+ seconds

  • Tzaangor (1 raw melee): Absurdly sensitive to melee; any melee contact pulls aggro immediately

  • Zoanthrope/Neurothrope (3 dmg/1.5s): Sensitive but ONLY to ranged weapon types. Melee, fire, and explosive don't trigger shield swap

  • Lesser Sorcerer: Has NO RangeThreat module. You cannot pull aggro by shooting — only melee (9 damage in 1.5s) triggers recovery

  • Hormagaunt/Termagant/Cultist: No recovery at all. Threat modules may still update the Enemy key through the selection pipeline, but there's no behavioral interrupt

The Recovery Trap

Forcing recovery can backfire:

  • Don't trigger Helbrute ranged threat recovery carelessly — it reduces its shooting cooldown by 15s

  • Hierophant recovery makes it shoot faster

  • Neurothrope recovery leads to a forced HEAVY counter-attack (Psychic Leech or Warp Ball)

  • Tzaangor Spearman: every 3rd recovery triggers Explosive Throw or Taunt (escalation)

Plan your threat-pulling around whether the recovery response is something your team can handle.

Game Files

  • {SERVER}/ssl/ai/enemy_selection/advanced_enemy_selection/sensors_observers/ — all detection sensors (vision, hearing, damage, ai_target, boss_sixth_sense, player_got_detected)

  • {SERVER}/ssl/ai/detection/rules/server/detection_rule_aggro_scream.sso — aggro scream propagation

  • {SERVER}/ssl/ai/detection/rules/server/detection_rule_aggro_brattle.sso — aggro brattle (ambient detection)

  • {SERVER}/ssl/ai/enemy_selection/advanced_enemy_selection/ — full enemy selection pipeline (controllers, sensors, utility functions)

  • {SERVER}/ssl/ai/behaviour/value_providers/object_value_provider_enemy.sso — the Enemy blackboard binding

  • {SERVER}/ssl/ai/knowledge/damage/ai_knowldege_module_range_threat.sso — default RangeThreat module

  • {SERVER}/ssl/ai/knowledge/damage/ai_knowldege_module_range_far_threat.sso — RangeFarThreat module (30-50m band)

  • {SERVER}/ssl/ai/knowledge/damage/ai_knowldege_module_melee_threat.sso — default MeleeThreat module

  • {SERVER}/ssl/ai/knowledge/damage/ai_knowledge_module_top_damage_threat.sso — TopDamageThreat (who's dealing the most overall)

  • {SERVER}/ssl/ai/behaviour/value_providers/float_value_provider_threat_density.sso — threat density by enemy classification

SM2 Melee Calculator Calculate exact damage for any weapon, variant, and perk combination against any enemy on any difficulty.
Open Calculator →