Legendary Weapon Dual-Tag Damage

Legendary weapons are produced by merging two level-20 non-legendary weapons. Each merge yields a weapon that inherits BOTH parent damage tags via damageTag (primary) and secondaryDamageTag (secondary) on its WeaponCoreSpec. The four base tags — bullet, energy, bomb, fire — pair with each other (including with themselves) for 10 unordered pairings; one legendary per pair.

Parent-pair table

Each legendary’s mergeParents is a canonical (alphabetical) pair of tags. Primary/secondary tag on the weapon spec matches that pair:

WeaponmergeParentsdamageTagsecondaryDamageTag
Mega Bullet[bullet, bullet]bulletbullet
Hellrain[bomb, bomb]bombbomb
Star Halo[energy, energy]energyenergy
Phoenix[fire, fire]firefire
4-Way Burst[bomb, bullet]bulletbomb
Wave Gun[bullet, energy]bulletenergy
Trailblazer[bullet, fire]bulletfire
Railstorm[bomb, energy]bombenergy
Carpet Bomber[bomb, fire]bombfire
Plasma Mortar[energy, fire]energyfire

What “dual-tag” means at fire time

When a legendary lands a hit, the collision resolver fires a bullet_hit signal carrying the weapon’s damageTag, then fires a SECOND bullet_hit signal carrying secondaryDamageTag — but only when the two tags differ:

// engine/combat/collision-resolver.ts (around line 137-142)
Sig.fire('bullet_hit', e.eid || 0, 0, b.dmg, 0, e._lastDmgTag);
// Legendary weapons carry a secondary damage tag; fire a matching signal so
// artifacts/effects keyed on EITHER parent tag react to the same hit.
if (_wSpec?.secondaryDamageTag && _wSpec.secondaryDamageTag !== _wSpec.damageTag) {
  Sig.fire('bullet_hit', e.eid || 0, 0, b.dmg, 0, _wSpec.secondaryDamageTag);
}

The guard _wSpec.secondaryDamageTag !== _wSpec.damageTag is the load-bearing detail. It splits the 10 legendaries into two cohorts:

Cross-tag pairs — fire TWO signals per hit

The six cross-tag legendaries (4-Way Burst, Wave Gun, Trailblazer, Railstorm, Carpet Bomber, Plasma Mortar) emit bullet_hit once with their primary tag and once with their secondary. Artifacts or effects that listen for bullet_hit filtered by tag will trigger off whichever tag they care about — and an artifact keyed on either parent tag will react to every legendary hit.

Same-tag pairs — fire ONE signal per hit

The four same-tag legendaries (Mega Bullet bullet+bullet, Hellrain bomb+bomb, Star Halo energy+energy, Phoenix fire+fire) skip the second emit. The secondaryDamageTag !== damageTag check rejects them so the bullet_hit signal fires exactly once per hit, carrying that tag. This prevents artifact triggers keyed on (for example) bullet hits from double-counting Mega Bullet rounds.

Downstream signals on kill

The dual-tag emit lives only in the collision resolver. Other signals along the kill chain read a single stamped tag (_lastDmgTag on the enemy) and fire once per kill:

  • damage_dealt — fired in damageEnemy carrying _lastDmgTag (the primary tag, stamped when the hit landed).
  • tagged_kill — fired in damageEnemy once the enemy’s HP reaches zero, carrying the same _lastDmgTag.

Neither of these doubles for cross-tag legendaries — only bullet_hit does. Artifacts that key on tagged_kill see the primary tag only.

Designing for the cohort split

Two implications fall out of this for designers tuning artifacts and mods that listen on bullet_hit:

  1. Cross-tag legendaries are double-counters. An artifact whose trigger condition is “on bullet_hit with tag = energy” fires twice per Plasma Mortar hit (once for energy primary, once for fire secondary). When designing trigger budgets, account for this 2× multiplier on cross-tag legendaries.

  2. Same-tag legendaries are single-counters. Mega Bullet does NOT double-stack the bullet artifact triggers despite being a bullet+bullet merge. To get extra triggers off Mega Bullet, use hit count, fire rate, or pierce count — not the dual-tag signal.

Source

  • src/starship-survivors/data/weapons/_types.tsWeaponCoreSpec.damageTag + secondaryDamageTag fields.
  • src/starship-survivors/data/weapons/legendaries.ts — all 10 legendary specs with mergeParents + dual-tag assignments.
  • src/starship-survivors/engine/combat/collision-resolver.ts (~L131-142) — the secondary-tag signal emit and the !== guard.
  • src/starship-survivors/engine/combat/damage.ts (~L341, L442) — damage_dealt and tagged_kill use _lastDmgTag (primary only).