bridge-death-debris.ts

3D death debris module — tumbling shard sprites ejected on enemy kill. Sprites pre-baked in atlas-builder.ts; this file owns spawn logic, archetype-driven tinting, and the active fragment pool.

What

Module exports a single spawn function (spawnDeathDebris) and an active deathDebris pool. Called from enemy-death handlers when an enemy is killed. Pushes randomized shard records into the pool capped at _MAX_DEBRIS = 60. Each shard is rendered as one batched sprite picking one of SHARD_VARIANT_COUNT = 6 pre-baked atlas regions.

Why

Kill burst is the primary feedback channel — it has to read at a glance even mid-swarm. The archetype-keyed tint map (Tick 44) and the archetype-keyed count multiplier (Tick 56) together encode mass + character: chargers throw aggressive orange-red, wisps throw a few magenta sparks, brutes throw heavy grey-brown chunks. The 50/50 bright/dark per-shard jitter preserves the pre-tick-44 red-bright/red-dark vibration look.

Constants

ConstantValueRole
DEBRIS_MIN_SPEED220Ejection speed floor (px/s)
DEBRIS_SPEED_RANGE180Ejection speed jitter band
DEBRIS_MIN_LIFE0.18Lifetime floor (s)
DEBRIS_LIFE_RANGE0.14Lifetime jitter band
_MAX_DEBRIS60Global pool cap
SHARD_VARIANT_COUNT6Atlas variants (must match atlas-builder.ts)
_DEFAULT_DEBRIS_TINTrL/gL/bL = 1.00/0.16/0.16, rD/gD/bD = 0.78/0.10/0.10Pre-tick-44 red fallback

DebrisTint type

type DebrisTint = { rL: number; gL: number; bL: number; rD: number; gD: number; bD: number };

Two-tone tint: L = bright, D = dark. Per-shard 50/50 weight at spawn.

ARCHETYPE_DEBRIS_TINT map

Tick-44 dispatch-site override-map (T18 dogfood, sibling to tick-42 audio and tick-43 VFX-burst).

ArchetypeBright (rL,gL,bL)Dark (rD,gD,bD)Character
orb1.00, 0.16, 0.160.78, 0.10, 0.10generic red (= default)
charger1.00, 0.40, 0.100.78, 0.25, 0.06aggressive orange-red
racer1.00, 0.90, 0.200.78, 0.62, 0.10neon yellow-electric
shooter0.30, 1.00, 0.300.18, 0.78, 0.18toxic green
gunner0.60, 1.00, 0.200.40, 0.78, 0.10green-yellow
mortar1.00, 0.55, 0.100.78, 0.35, 0.06explosive orange
sniper0.75, 0.30, 1.000.50, 0.18, 0.78beam purple
field0.30, 0.90, 1.000.18, 0.62, 0.78field cyan
brute (T25/26)0.60, 0.45, 0.320.40, 0.28, 0.20dark grey-brown chunks
wisp (T25/26)1.00, 0.50, 1.000.78, 0.32, 0.78ethereal magenta
lurker (T25/26)0.55, 0.20, 0.800.35, 0.12, 0.55deep dark-purple
bombardier (T25/26)1.00, 0.75, 0.200.78, 0.50, 0.10bright explosive amber
sprinter (T48)0.78, 1.00, 1.000.50, 0.78, 0.78supersonic cyan-electric
spitter (T50)0.70, 1.00, 0.200.50, 0.78, 0.10acid yellow-green
burner (T51)1.00, 0.50, 0.150.78, 0.32, 0.08fire/lava red-orange
suppressor (T52)0.55, 0.70, 0.300.35, 0.50, 0.18industrial olive-drab

Missing archetypes fall back to _DEFAULT_DEBRIS_TINT.

ARCHETYPE_DEBRIS_COUNT_MULT map

Tick-56 override-map (6th T19 instance). Multiplies the caller-supplied count before flooring at 1.

ArchetypeMultRationale
gunner0.8small turret
mortar1.3(orb/charger/racer/shooter/sniper default 1.0)
field1.4big emitter
brute1.5heavy chassis
wisp0.4tiny ethereal — almost no shards
lurker1.1slightly heavier than sniper
bombardier1.5big explosive payload
sprinter0.5fragile + supersonic
spitter1.2tanky standoff mass
burner1.4big static emitter (matches field)
suppressor1.3tanky turret

All others default to 1.0.

DeathDebris interface

interface DeathDebris {
  x: number; y: number;
  vx: number; vy: number;
  rot: number; rotSpd: number;
  size: number;
  life: number;
  maxLife: number;
  variant: number;   // 0..SHARD_VARIANT_COUNT-1, picks atlas region
  r: number; g: number; b: number;  // per-shard vertex tint
}

Pool exported as export const deathDebris: DeathDebris[].

spawnDeathDebris

export function spawnDeathDebris(
  wx: number, wy: number,
  radius: number,
  count: number,
  behavior?: string
): void

Per call:

  1. Look up tint from ARCHETYPE_DEBRIS_TINT[behavior] (default red on miss).
  2. Look up countMult from ARCHETYPE_DEBRIS_COUNT_MULT[behavior] (1.0 on miss).
  3. scaledCount = Math.max(1, Math.round(count * countMult)) — floors at 1 so tiny enemies still emit at least one shard.
  4. Loop up to scaledCount, short-circuit if pool already at _MAX_DEBRIS.
  5. Per shard: random angle, spd ∈ [220, 400), life ∈ [0.18, 0.32), position offset ±0.2*radius on each axis, rotation jitter ±11 rad/s (rotSpd = (rand-0.5)*22), size = 4..8 + 0.10*radius, variant = floor(rand * 6), bright = rand < 0.5 picks bright vs dark tint channel.

Spawn position scatter uses radius * 0.4 width ((rand-0.5) * radius * 0.4).

Render / lifetime

This module owns spawn + storage only. Update (velocity, rotation, life decrement) and render (one batched sprite per shard, atlas lookup by variant) live elsewhere — search for consumers of deathDebris and SHARD_VARIANT_COUNT. Atlas-side variants are baked at startup in atlas-builder.ts; SHARD_VARIANT_COUNT must stay in sync with that file.

Touchpoints

  • Atlas: src/starship-survivors/engine/atlas-builder.ts bakes the 6 red shard sprite variants; consumers tint them per-shard via the r/g/b fields.
  • Callers: enemy death handlers pass behavior from the killed enemy’s archetype string.
  • Sibling override-maps: tick-42 audio + tick-43 VFX-burst follow the same dispatch-site override-map pattern; this is the third (tint) and sixth (count) T18/T19 instance respectively.

EXTRACT-CANDIDATE

  • Archetype maps belong in data, not code. Both ARCHETYPE_DEBRIS_TINT and ARCHETYPE_DEBRIS_COUNT_MULT are content tables hard-coded in a TS file with tick-comments. Per CLAUDE.md (“Content in data tables, behavior in code”), these should move to a per-archetype data row (e.g. data/archetypes.json or data/archetypes.ts) alongside the tick-42 audio + tick-43 VFX-burst maps. One archetype row, N override columns; the spawn function reads the row.
  • Tick-comment noise. The “Tick 44 — Strict T18 dogfood of the dispatch-site override-map pattern (tick-42 audio, tick-43 VFX-burst)” prose is process metadata, not code. Move to an ADR; leave a one-line pointer.
  • Magic 0.4 / 0.10 / 22 / 0.5 scatter constants in spawnDeathDebris are unnamed. Per CLAUDE.md (“Every number traces to a named constant”), promote: DEBRIS_POS_SCATTER = 0.4, DEBRIS_SIZE_RADIUS_COEF = 0.10, DEBRIS_ROT_SPD_RANGE = 22, DEBRIS_BRIGHT_PROB = 0.5.
  • SHARD_VARIANT_COUNT duplicated across files. Source of truth comment says “must match atlas-builder.ts” — that’s a foot-gun. Either import from the atlas module or move both to a shared constants module.
  • Top comment says “3D death debris — tiny tumbling cubes” but the inline comment on line 6 says “2D red polygon shards.” Code is 2D shards. Fix the top comment.
  • _MAX_DEBRIS cap silently drops shards when the pool is full (the for-loop short-circuits). Either documented behavior (current state) or recycle oldest — pick one and note it.