Burst Fire Rate Semantics — Bursts/sec vs Shots/sec

For burst-pattern weapons, the displayed fireRate is bursts-per-second, not shots-per-second. Each burst (cast / cycle / trigger) contains N sub-shots fired in quick succession at an intra-burst interval, so the actual rate at which damage rolls hit enemies is far higher than the stat sheet number suggests.

This distinction is invisible to most weapons (semi-auto kinetics fire one shot per trigger, so bursts/sec ≡ shots/sec) but matters whenever a weapon uses the burst_fire behavior or has a projectileCount greater than one per cast.

The two fire-rate dialects

Every weapon in WeaponCoreSpec exposes fireRate: { base, scaling }. That number is always casts-per-second — how often the trigger pulls. What changes between weapon families is how many damage events come out of one trigger pull:

  • Semi-auto / single-projectile weapons (e.g. coilgun, cannon): one trigger pull → one shot. Stat sheet fireRate = shots-per-second directly.
  • Multi-shot-per-cast weapons (e.g. Burst — the uncommon hitscan, data/weapons/burst.ts): one trigger pull → projectileCount simultaneous hitscan beams targeted at the nearest enemies. Stat sheet fireRate = casts-per-second; true shots/sec = fireRate × projectileCount. All sub-shots resolve on the same frame.
  • Burst-pattern weapons (e.g. Revolver — the epic, data/weapons/revolver.ts): one trigger pull → coordinator bullet that spawns burstCount sub-shots spaced intraBurstDelay apart over time. Stat sheet fireRate = bursts-per-second; true shots/sec = fireRate × burstCount, but spread across (burstCount − 1) × intraBurstDelay seconds rather than instant.

True shot DPS (burst-pattern)

For a burst_fire weapon:

True shots/sec = fireRate (bursts/sec) × burstCount (shots/burst)
True shot DPS  = fireRate × burstCount × damage-per-shot

The Revolver illustrates this clearly. At L1 (data/weapons/revolver.ts):

  • fireRate.base = 0.768 bursts/sec
  • burstPattern[L1] = 6 shots/burst
  • damage.base = 90 per shot
  • True shots/sec ≈ 4.6
  • True shot DPS ≈ 414

The cast-DPS reading (fireRate × damage ≈ 69) underestimates the weapon by 6× because damage is per sub-shot, not per burst.

By L20 the gap widens further: burstPattern[L20] = 24 shots/burst, so a single burst now drops 24 separately-resolved damage events into the world, each able to crit, splash, trigger on-hit effects, and stack any per-shot procs.

How a burst plays out frame-by-frame

WeaponManager.fireBurst (engine/weapons/weapons.ts:1205) is the entry point for any weapon with behavior: 'burst_fire'. It does not fire the sub-shots directly — instead it spawns an invisible coordinator bullet anchored to the ship, then the burst_fire bullet behavior (engine/weapons/bullets.ts:691) advances a per-shot timer and emits one hitscan beam-trace sub-shot every intraBurstDelay:

  1. Trigger pulls (gated by fireRate). fireBurst reads burstCount from the stepped burstPattern table and intraBurstDelay from cadenceStepSec (falling back to 0.045 s). It spawns a coordinator bullet with _burstShotsLeft = burstCount, _burstTimer = 0 (first shot fires immediately), and a lifetime of burstCount × intraBurstDelay + 0.12 s.
  2. Coordinator update (every frame, while alive). The burst_fire behavior follows the ship, decrements _burstTimer, and when it hits zero:
    • Re-acquires the closest enemy in acquireRange (only on the first sub-shot — subsequent sub-shots keep the locked target unless it dies)
    • Spawns one beam-trace bullet aimed at the locked target with ±1.5° jitter, dealing _burstDmg damage per shot
    • Resets _burstTimer = _burstIntraSec, decrements _burstShotsLeft
  3. Coordinator expires once all sub-shots have fired (lifetime ≈ burstCount × intraBurstDelay + 0.12 s).

The next burst doesn’t start until 1 / fireRate seconds after the previous trigger pull (not after the previous burst finishes). On slow-firing high-burst-count weapons the bursts can overlap if burstCount × intraBurstDelay > 1 / fireRate.

Why this matters for design and balance

  • Damage-per-shot scaling is multiplicative with burst count. A +10% damage upgrade on a 24-shot burst is worth 24× more raw damage per cast than the same upgrade on a 1-shot semi-auto. Burst weapons concentrate scaling.
  • On-hit procs roll per sub-shot, not per cast. Crit, lifesteal, bullet-tag blood splatters, splash AoE — each sub-shot is its own resolution. Burst weapons are disproportionately strong with proc-based modifiers.
  • Stat sheets undersell burst DPS. Any tooltip showing fireRate × damage is reading cast-DPS, not shot-DPS. Burst weapons appear weaker than they are on paper unless the display layer multiplies by burstCount (the Playground WeaponsTab shows the raw cadence pattern so the math is visible).
  • intraBurstDelay is a feel knob, not a balance knob. Total damage out of a burst is burstCount × damage regardless of intra-burst spacing; cadenceStepSec only controls how stretched the burst feels (0.06 s for the Revolver reads as a fast trigger pull, 0.30 s would read as a controlled tap-tap-tap).
  • Burst can miss mid-sequence. If the locked target dies before all sub-shots fire, the coordinator re-acquires. If no targets remain in acquireRange, the remaining sub-shots still spawn but waste against empty space — burst weapons over-commit to dead targets in low-density situations.

Distinguishing the two “Burst” things

The codebase has two unrelated weapons with “burst” in the name and it’s easy to confuse them:

  • Burst (data/weapons/burst.ts, uncommon, family sniper) — does not use burst_fire behavior. It’s an instant hitscan that fires projectileCount shots simultaneously at the nearest enemies (3 at L1). All sub-shots resolve on the same frame; cadenceStepSec is 0.0. This is a multi-shot-per-cast weapon, not a burst-pattern weapon.
  • Revolver (data/weapons/revolver.ts, epic, behavior: 'burst_fire') — the canonical burst-pattern weapon. 6–24 sub-shots per burst depending on level, spaced 0.06 s apart, each homing toward the locked target.

Only the second uses the bursts/sec vs shots/sec semantics described here. The first is closer to a shotgun: the displayed fireRate already counts all of projectileCount worth of shots as one trigger pull, but they all land at the same instant rather than over time.

Source references

  • engine/weapons/weapons.ts:1205WeaponManager.fireBurst, the cast-time spawner for burst_fire weapons
  • engine/weapons/bullets.ts:691burst_fire bullet behavior, the per-frame sub-shot emitter
  • data/weapons/revolver.ts — canonical burst_fire weapon with burstPattern stepped scaling
  • data/weapons/burst.ts — multi-shot-per-cast hitscan (NOT burst_fire); kept here as a name-collision warning
  • data/weapons/_types.ts:292cadenceStepSec field definition