Bullet archetypes

What this is

A bullet archetype is a short string tag attached to every spawned projectile that selects which visual branch the renderer draws for that bullet. It is purely a cosmetic / VFX routing key — the gameplay-side family field still drives the fire pipeline, collision, scaling, and damage. The archetype only tells drawBullet which if (arch === ...) branch to take.

The tag is stored on the weapon spec as the optional field bulletArchetype: string on WeaponCoreSpec. When a weapon fires, the fire pipeline copies that string onto each spawned bullet entity as barrel.archetype. When the renderer iterates world.playerBullets, it reads b.archetype and routes to the matching draw branch. Two unrelated weapons can declare the same bulletArchetype and share the same visual, decoupling visual identity from weapon balance — Coilgun reuses Rifle’s pulse_ball, Magnetar reuses Line’s tesla_line, Fire Ring reuses Sweep’s sweep_laser, and the bullet+fire legendary Trailblazer reuses Rifle’s pulse_ball too.

When bulletArchetype is omitted on a weapon, the fire pipeline falls back to a context-appropriate default:

Fire pathFallback archetype
Standard projectile spawnThe weapon’s family string (projectile, explosive, sniper, beam, chain, homing, sweep)
Arc-mortar spawn (cannon, mortar)mortar_shell
Tesla-line spawn (line family)tesla_line
Shield-arc spawn (barrier family)shield_arc

Bullets that arrive at the renderer with an archetype string that doesn’t match any branch fall through to the final else clause and draw as a colored circle with a white center.

The archetype registry

There is no central registration list — the renderer is the registry. Each archetype is implemented as one branch of the if / else if chain inside drawBullet in src/starship-survivors/engine/rendering/draw.ts, and a string only “exists” as an archetype if a matching branch exists there. Adding a new archetype means adding a new else if (arch === 'newname') branch and pointing one or more weapon specs at it.

The branches currently implemented:

ArchetypeVisual silhouetteUsed by
pulse_ballCircular energy orb with white-hot corerifle, coilgun, lgd_incendiary_rifle (Trailblazer)
cannonballOversized black iron ball with orange glow + white outline + specular highlightcannon
projectileElongated cyan capsule with white highlightfamily fallback for projectile weapons without an explicit archetype
explosiveChunky dark shell with orange nose conefamily fallback for explosive weapons
sniperFull-range beam with layered electrical glow, muzzle flash, impact flash, glitch lines, ghost-scar afterimagefamily fallback for sniper weapons (railgun, death ray)
beamPulsing multi-layer beam with solid corefamily fallback for beam weapons
chainBlue electric orb with eight crackling lightning arcsfamily fallback for chain weapons
homingPrebaked rocket sprite (red tip, silver body, black wings) + warm halofamily fallback for homing weapons (missiles)
mortar_shellOversized fireball with red-orange glow, white outline, ground shadow, fake-3D arc heightarc-mortar fallback (mortar)
blade / sweep_laserBeam drawn from ship center along orbit angle with crackle, endcap orb, mini-arcssweep, fire_ring
emberOversized teardrop pointing along travel, fully additiveflame family
disc_ringBoomerang plasma ringdisc
shield_arcOrbiting energy arc with thickness from _arcThicknessbarrier
tesla_lineTwo orbiting balls connected by a thick electric lineline, magnetar
mega_bulletSlow brass-trail bullet bodylgd_autocannon (Mega Bullet)
plasma_arcCurved blue plasma wavelgd_railslug (Wave Gun)
quad_roundWhite machine-gun tracerlgd_flak_rifle (4-Way Burst)
phoenix_pulseContinuous fire aura with phoenix outlinelgd_inferno (Phoenix)
plasma_ballBlue plasma mortar shelllgd_arc_flame (Plasma Mortar)
plasma_fire_zonePersistent blue-fire damage patchlgd_arc_flame ground zone
fire_patchPersistent fire trail damage zonelgd_incendiary_rifle ground zone
carpet_bomberScreen-pinned bomber spritelgd_napalm_mortar (Carpet Bomber)
star_halo_rootRing-of-stars orbit formationlgd_railstorm (Star Halo) declared as star_orbit
beam_decayDecaying beam sprite with cascade explosionslgd_railstorm (Railstorm) variant
artillery_strikeTelegraph circle + falling shelllgd_blackhole_cluster (Hellrain)
fallbackSolid colored circle with white centerany unrecognized archetype string

Weapons that explicitly declare bulletArchetype in their spec:

WeaponbulletArchetype
riflepulse_ball
coilgunpulse_ball
cannoncannonball
linetesla_line
magnetartesla_line
barriershield_arc
discdisc_ring
sweepsweep_laser
fire_ringsweep_laser
lgd_autocannon (Mega Bullet)mega_bullet
lgd_railstorm (Star Halo)star_orbit
lgd_flak_rifle (4-Way Burst)quad_round
lgd_railslug (Wave Gun)plasma_arc
lgd_incendiary_rifle (Trailblazer)pulse_ball
lgd_arc_flame (Plasma Mortar)plasma_ball

Renderer behavior

The renderer consumes the archetype tag in one place: the drawBullet function. Per bullet, per frame, the renderer:

  1. Reads b.archetype off the spawned bullet entity (the string copied from the weapon spec’s bulletArchetype at spawn time, or the family/path fallback).
  2. Scales the visual size by the camera zoom, a fixed BULLET_VISUAL_SCALE factor of 1.04, and — for legendary weapons (weaponId prefix lgd_) — a level-driven shrink factor from 0.15 at level 1 to 0.50 at level 20.
  3. Optionally applies a 0.55 alpha multiplier for legendary weapons (further damping VFX intensity at high levels).
  4. Walks the if (arch === 'pulse_ball') / else if (arch === 'cannonball') / ... chain and runs the matching draw branch, which handles its own composite operations, glow layers, sprite stamps, body shape, and overlays.
  5. If no branch matches, draws the fallback solid colored circle with a white center.
  6. After the branch returns, runs a unified post-pass additive neon glow stamp around the bullet, except for archetypes that draw their own beam/long-form glow inline: sniper, star_halo_root, beam_decay, artillery_strike, phoenix_pulse, plasma_fire_zone, fire_patch, carpet_bomber, mega_bullet. For cannonball, the post-pass glow radius is multiplied by 1.8 to match its oversized body.

The renderer also gates a now-disabled universal glow halo on the archetype tag — a hardcoded skip list of sniper, orbit, gravity_mine, shield_arc, tesla_line, homing, mortar_shell prevents the halo from drawing on archetypes that already render their own beam, mine, or arcing visual.

Visual size, colors, and bullet phase data are read off the bullet entity itself (b.radius, b.c1, b.c2, b._arcHeight, b._beamAngle, b._beamWidth, b._orbitAngle, etc.). The archetype tag does not carry any data of its own — it is purely a switch key into the draw chain.

Reuse across weapons

The archetype-as-string design means visual identity is decoupled from weapon ID. Four explicit reuse cases ship today:

Reusing weaponReuses archetype fromTag
coilgunriflepulse_ball
magnetarlinetesla_line
fire_ringsweepsweep_laser
lgd_incendiary_rifle (Trailblazer)riflepulse_ball

In each case the reusing weapon has different damage tag, color palette, fire rate, family scaling, and gameplay role, but draws the same projectile silhouette as its donor weapon. The renderer reads b.c1 / b.c2 (the weapon’s color1 / color2) inside most branches so the same shape draws in different colors per weapon — Coilgun’s pulse_ball is violet, Rifle’s is silver-white, Trailblazer’s is orange.

Authoring a new weapon that should look like an existing one is a single-field change: copy the donor’s bulletArchetype value onto the new spec, set the new color palette, and the renderer requires no edits. Authoring a new visual identity requires adding both a new branch in drawBullet and a new tag string on the spec — the tag space is open and the renderer’s fallback-circle clause is the safety net for any spec/renderer drift.