Sub-Projectile Types

Legendary weapons spawn secondary bullets and AoE bursts that aren’t in the player weapon registry. They live inside bullets.ts (src/starship-survivors/engine/weapons/bullets.ts) as world.playerBullets.push({...}) calls fired from a parent behavior’s update / onDeath / onHit hook, or as delayed-detonation bursts scheduled through the spawnDelayedAoE / spawnFlameZone primitives in engine/effects/custom-handlers.ts. Each sub-projectile carries an arch: tag (rendered as that archetype) plus a _behaviors: [...] list that tells the bullet runner which handlers to drive it with. Damage credit still flows back to the parent weaponId for telemetry.

The pool is capped: every spawn site is guarded by if (world.playerBullets.length < 100) to keep the bullet array bounded. Drops past 100 are silently lost.

Bullet-pushed sub-projectiles (carry arch: + _behaviors:)

These are real Bullet records with their own lifetime, position, velocity, and damage. They flow through the standard collision resolver and renderer.

arch tagSpawned byParent legendaryWhat it does
plasma_fire_zoneplasma_mortar_land.onDeath (bullets.ts:2688)Plasma Mortar (lgd_arc_flame)Stationary radius zone, rad: 0, lives 3 s, ticks energy damage 4×/sec to any enemy in _zoneRadius (= 80 % of the shell’s blast radius). Per-tick dmg = ceil(parent.dmg × 0.10). Runs plasma_fire_zone behavior.
plasma_dropletplasma_mortar_land.onDeath (bullets.ts:2747)Plasma Mortar (lgd_arc_flame)Three small cyan plasma droplets thrown radially at 280 px/s within a 60° spread from impact. Lifetime 0.3 s, rad: 25, first-hit collision, ~20 % of main damage. Runs plasma_arc behavior for visuals.
fire_patchcarpet_bomber.update per bomb drop (bullets.ts:2913)Carpet Bomber (lgd_napalm_mortar)Persistent ground fire patch dropped at every bomb landing site. Lives 2.5 s, ticks 5×/sec at _zoneRadius = 70 % of bomb blast radius. Per-tick dmg = ceil(bombDmg × 0.06). Runs fire_patch_zone behavior.
emp_ringempWave.onDeath (bullets.ts:790)(Non-legendary EMP weapons — listed for completeness)Expanding ring AoE, lives 2 s, growing to _empMaxRad = parent.blastRadius, deals 80 % of parent damage. Not currently used by any legendary; the slot lives in the same registry path.
sniperburst_fire.update per sub-shot (bullets.ts:746)(Burst-fire base weapons)Per-burst hitscan beam-trace bullet, lifetime 0.06 s, pierceCount: 999, _collisionMode: 'beam_trace'. The Mega Bullet legendary uses mega_bullet_trail instead and does not invoke burst_fire, so this arch is base-weapon only.
nova_ringperiodicRing.update (bullets.ts:114)(Periodic-ring archetype)Stationary ring, no damage, 0.25 s life, 3× the parent’s radius. Used by non-legendary periodic-pulse weapons.

The two zone-archetype bullets (plasma_fire_zone and fire_patch) are special: they have vx/vy = 0, rad: 0 (so the bullet-vs-enemy radius check never trips), and apply damage themselves inside their update handler via enemyGrid.query + damageEnemy. They are sub-projectiles only in the technical sense — really they are persistent damage zones riding the bullet pool.

Coordinator bullets and their virtual sub-projectiles

Some legendaries spawn one coordinator bullet that internally tracks many virtual “stars” or “stages” without each one being a real Bullet push. These don’t use arch: for the children — the parent owns the array.

  • Star Halo (lgd_railstorm, orbit_ring behavior, bullets.ts:1802). The cast spawns a single coordinator bullet. Inside b._ringStarsList[] are 5–30 plain JS objects (one per “star”), each with its own {x, y, slotAngle, phase, contactCooldowns}. The coordinator drives all of them through stream → spin → burst_out → explode phases. Per-star explosion damage uses b._ringStarDmg (= spec.damage). Spin-phase sweep damage = 25 % of that, with per-star × per-enemy cooldown of one revolution.
  • Railstorm (lgd_plasma_launcher, beam_decay behavior, bullets.ts:2069). The beam bullet stores _bdExplosionCount (6–14 by level via beamDecayExplosions) and schedules them as raw AoeExplosion.spawn + per-enemy damage loops over a 2 s decay window. No sub-bullets are pushed — each cascade is a position + radius + damage triplet evaluated inline.
  • Hellrain (lgd_blackhole_cluster, artillery_rain behavior, bullets.ts:2182). The bomb itself is one bullet with two phases (telegraphfall). The aoe_finish damage on landing comes from the standard aoe_finish behavior on the same bullet — not a new sub-projectile.
  • Carpet Bomber (lgd_napalm_mortar, carpet_bomber behavior, bullets.ts:2823). The bomber is the coordinator. Each bomb drop applies AoE damage inline (no sub-bullet for the explosion itself), then pushes one fire_patch zone bullet (above) and one spawnFlameZone patch for the lingering-flames artifact stack.

Delayed AoE “echoes” (not bullets at all)

Several legendaries layer in a thematic “echo” — a small delayed burst that detonates after the main effect. These are pushed into a private _delayedAoEs[] array in engine/effects/custom-handlers.ts via spawnDelayedAoE(x, y, radius, damage, delay, c1, c2) and are ticked by tickDelayedAoEs(dt). They have no projectile body, no collision, no arch. On detonation they paint an AoeExplosion + impact ring and damage every enemy in radius once with distance falloff. The pool is hard-capped at DELAYED_AOE_MAX (oldest shifts out).

Echo nameParent legendaryWhere spawnedPattern
Cluster BombletHellrain (lgd_blackhole_cluster)artillery_rain.update impact frame (bullets.ts:2265)One delayed AoE at the landing site after 0.4 s. Radius = 50 % of bomb blast, damage = 30 % of primary AoE damage.
Sparkle WakeStar Halo (lgd_railstorm)orbit_ring.update explode phase (bullets.ts:2040–2041)Two tiny 15 px pulses per exploding star, at +0.20 s and +0.40 s. Per-pulse dmg = ceil(starDmg × 0.04).
Lingering ScarRailstorm (lgd_plasma_launcher)beam_decay.onDeath (bullets.ts:2155)Six sample points along the beam path, each fires twice (at +0.05 s and +0.25 s). Radius = max(20, beamWidth × 0.35). Per-pulse dmg = cascade damage × 0.08.
Bomb PyreCarpet Bomber (lgd_napalm_mortar)carpet_bomber.update per bomb drop (bullets.ts:2910)Calls spawnFlameZone(landX, landY, 50, dmg × 0.20, 0.8) — a global-pool flame zone rather than a delayed AoE, but conceptually the same “echo” slot. Stacks with the lingering_flames artifact.
Slipstream WakeMega Bullet (lgd_autocannon)mega_bullet_trail.onDeath (bullets.ts:2283)Implemented inline (no helper call) — does its own 40 × 12 px oval damage check behind the slug at expiry, 25 % of slug damage. Listed here because it occupies the same “thematic echo” design slot.

Legendary → sub-projectile coverage

LegendaryReal sub-bulletsVirtual coordinator childrenDelayed-AoE / flame-zone echoes
Mega Bullet (lgd_autocannon)Slipstream Wake (inline oval)
Hellrain (lgd_blackhole_cluster)Cluster Bomblet
Star Halo (lgd_railstorm)5–30 stars in _ringStarsListSparkle Wake × 2 per star
Phoenix (lgd_inferno)phoenix_pulse ring is a single coordinator
4-Way Burst (lgd_flak_rifle)Hitscan only (quad_burst dispatcher)
Wave Gun (lgd_railslug)
Trailblazer (lgd_incendiary_rifle)— (flame patches handled by the fire_trail weapon system, not as sub-bullets)
Railstorm (lgd_plasma_launcher)6–14 cascade explosions inlineLingering Scar × 12
Carpet Bomber (lgd_napalm_mortar)fire_patch × 6–18 (one per bomb)6–18 bombs (shellCount)Bomb Pyre × N (one per bomb, via spawnFlameZone)
Plasma Mortar (lgd_arc_flame)plasma_fire_zone × 1–3, plasma_droplet × 3 (per shell)1–3 shells (shellCount)

Why not just register them as weapons?

These archetypes never appear in the player-facing weapon registry because they:

  • have no acquire/fire pipeline (they don’t pick targets or schedule next-shot timers — the parent does)
  • have no level-up entry (their stats are derived from the parent’s dmg, blastRadius, etc., at spawn time)
  • have lifetimes counted in fractions of a second, designed as cleanup VFX-with-damage rather than independent weapons
  • collide using the same _collisionMode + _canHitDestructibles flags as a regular bullet, so they piggyback on the resolver instead of adding a new path

Bullet pool / archetype catalog details live in bullet-archetypes and bullet-pool-recycle. Zone-tick mechanics are detailed in plasma-mortar-zones, carpet-bomber-zones, and flame-zones.