Lurker
What it is
A long-windup variant of the standard sniper archetype. The Lurker is a mechanical, tanky enemy that orbits the player at long range, paints a red dot forecast on the player during an extended charge, then fires a single very fast projectile that ignores terrain. Compared to the baseline sniper it declares a longer base charge time (4.0 seconds versus 2.5) and a heavier base beam damage (10 versus 5), trading lower HP and a slower base move speed for a much harder hit. The longer wind-up is a stronger telegraph but punishes positioning mistakes more severely. The Lurker’s archetype tag is its own (lurker), not sniper, even though it reuses the sniper movement and combat behavior pipeline.
Base stats
These are the values declared on the Lurker base archetype before any per-rarity scaling.
| Field | Value |
|---|---|
| HP | 60 |
| Move speed | 22 |
| Hit radius | 6 |
| XP on kill | 35 |
| Beam damage (declared base) | 10 |
| Charge time (declared base) | 4.0 s |
| Orbit radius | 240 |
| Weapon | enemy sniper |
| Tags | mechanical, tanky |
Per-rarity stats
HP, move speed, hit radius, and XP scale via the shared rarity multiplier table. The shared per-archetype scaling pipeline contains a special block that scales sniperChargeTime and sniperBeamDamage per rarity, but that block is gated on archetype === 'sniper' and the Lurker’s archetype string is lurker, so neither field is copied onto the rarity-specific type definition. At runtime the sniper-behavior combat phase reads def.sniperChargeTime and def.sniperBeamDamage with fallback constants of 2.5 seconds and 2 damage when those fields are missing on the def — so every Lurker rarity that the game can actually spawn fires with the sniper fallback values rather than the Lurker’s declared base values. The table below reflects the values that are actually live at runtime.
| Rarity | HP | Move speed | Hit radius | XP | Beam damage (runtime) | Charge time (runtime) |
|---|---|---|---|---|---|---|
| Common | 162 | 31 | 6 | 105 | 2 | 2.5 s |
| Uncommon | 297 | 32 | 7 | 210 | 2 | 2.5 s |
| Rare | 416 | 35 | 8 | 420 | 2 | 2.5 s |
| Epic | 594 | 37 | 8 | 630 | 2 | 2.5 s |
| Legendary | 713 | 40 | 9 | 840 | 2 | 2.5 s |
All rarities share the same behavior, weapon, orbit radius, and tag set. Tier color is set by the shared rarity tint palette.
Behavior
The Lurker is registered under the sniper behavior dispatch, so it inherits the full sniper movement-and-combat pipeline.
Movement uses a car-steer model. Outside aggro the Lurker drifts with idle friction. Once aggroed, it tries to hold an orbit at its orbit radius of 240: when the player is farther than 1.5× orbit radius it accelerates toward the player, when the player is closer than 0.6× orbit radius it steers away, otherwise it coasts with idle friction. While aiming, the Lurker freezes in place under stop-friction and faces the player.
Combat is a three-phase finite state machine: positioning, aiming, cooldown.
- Positioning — the Lurker orbits at range. A short between-shots timer counts down. When the timer reaches zero, if the player is inside the maximum engagement range the Lurker enters the aiming phase and spawns a red-dot forecast that tracks the player throughout the charge.
- Aiming — a charge timer ramps up while the Lurker is frozen and facing the player. A damage flash on the enemy ramps from 50 percent to 100 percent over the course of the charge as a visible wind-up cue. If the player moves outside the maximum engagement range during the charge, the shot is canceled, the forecast is cleared, and the Lurker enters a half-length cooldown.
- Cooldown — after firing or after a canceled charge, a cooldown timer counts down. When it reaches zero the Lurker returns to positioning.
On charge completion the Lurker fires a single high-speed projectile from its position straight at the player’s current position. The projectile is flagged to ignore terrain — it passes through obstacles. Firing also applies a fixed backward recoil impulse along the negative aim vector, a brief muzzle flash, and a short-lived beam VFX line from the Lurker to the target point. The Lurker is stunnable and knockbackable. On death any remaining red-dot forecast owned by the Lurker is removed from the world.
Weapon parameters
These are the shared sniper-behavior constants — they are not parameterized per archetype, so they apply identically to the Lurker.
| Parameter | Value |
|---|---|
| Cooldown between shots | 3.0 s |
| Cancel cooldown (player escapes during aim) | 1.5 s |
| Projectile speed | 800 px/s |
| Maximum engagement range | 600 |
| Projectile max travel distance | 700 |
| Projectile radius | 5 |
| Recoil impulse | 250 |
| Beam VFX duration | 0.15 s |
| Terrain collision | none — passes through obstacles |
| Forecast type | red dot tracking the player |
Elite affix
The Lurker archetype is mapped to the reflective_burst affix in the per-archetype elite-affix table. When a Lurker rolls as an elite via the affix system, this is the affix it carries.
Spawning
The Lurker is registered as the 11th base archetype but no planet enemy-set ID references the Lurker by id, and no spawn pool maps a lurker_{rarity} type to a slot. The Lurker therefore does not appear in any of the live per-planet spawn pools.
Tier scaling
HP, move speed, hit radius, and XP scale with the shared rarity multiplier table. Beam damage and charge time do not scale per rarity for the Lurker — they remain at the sniper-behavior runtime fallback values (2 damage, 2.5 second charge) across every rarity because the per-rarity scaling block is gated on the sniper archetype string. Behavior parameters (orbit radius, projectile speed, between-shots cooldown, recoil, maximum range, projectile travel distance, beam VFX duration) are shared constants and do not scale with rarity.
EXTRACT-CANDIDATE: The shared rarity-multiplier table (HP 2.7→11.88, XP 3→24, speed 1.40→1.82, radius 1.0→1.5, damage 3.0→10.5) is repeated on every enemy-entity page; canonicalize on the enemies rollup.
EXTRACT-CANDIDATE: The car-steer movement model and the three-phase sniper combat FSM are shared by sniper and lurker (and the FSM pattern matches charge-then-fire telegraphed enemies generally); belongs on a shared enemy-behavior or movement-models page.
EXTRACT-CANDIDATE: The sniper-behavior constants (3.0 s cooldown, 800 px/s projectile speed, 600 max range, 700 projectile travel, 250 recoil, 5 projectile radius, 0.15 s beam VFX) are duplicated between sniper and lurker entity pages and never differ; belongs on a single sniper-behavior reference table.
EXTRACT-CANDIDATE: The lurker base archetype declares sniperChargeTime: 4.0 and sniperBeamDamage: 10, but the per-rarity scaling block in data/enemies/index.ts is gated on base.archetype === 'sniper' and the lurker’s archetype string is lurker, so those declared values are never copied onto any rarity-specific EnemyTypeDef. At runtime the sniper behavior reads def.sniperChargeTime || 2.5 and def.sniperBeamDamage || 2, so every live Lurker fires at 2.5 s / 2 dmg instead of the intended 4.0 s / 10 dmg. Likely-bug candidate — either the scaling gate should also accept archetype === 'lurker', or the lurker’s per-rarity overrides should be wired through a separate block. Flag for review.
EXTRACT-CANDIDATE: The lurker is registered as a base archetype but no planet enemy-set ID references it and no spawn pool maps lurker_{rarity} to a slot — the archetype is currently unspawned. Flag alongside the scaling-gate issue: same likely root cause (the lurker was added but never fully wired into the per-rarity scaling pipeline or any spawn set).