Boss Spawn Profiles
A boss spawn profile is the timeline of pressure adds that runs alongside a
boss encounter — answering what / how many / when / where / how often. The
data shapes live in src/starship-survivors/data/spawn-profiles.ts; the runtime
that ticks the active profile and fires waves lives in
src/starship-survivors/engine/enemies/boss-spawn-profile.ts.
Pressure adds spawned by a profile are normal enemies — they drop XP, fire
enemy_kill, count toward streak, and do not contribute to the boss bar
(they never set sharesHealthWithBoss). The profile only describes the
schedule; the actual spawn lands through the standard GameMaster.spawnEnemy
path so all the usual enemy systems apply.
Lifecycle
game.bossSpawnProfileholds the active profile id while the encounter is live (ornull/'none'when there is none).game.bossEncounterTimeis the encounter clock the runtime advances every tick.resetBossSpawnProfile()is called at encounter start, encounter end, and on player death, so a fresh encounter never inherits stale fire counts from a previous one.- An unknown profile id throws — there is no silent fallback.
See spec §3.4 and §6 (spawn profile lifecycle).
The six profiles
| Profile | Role | Wave summary |
|---|---|---|
none | No pressure | Empty waves array — boss fights solo. |
light_pressure | Background harassment | 3× orb_common from arena edge every 8 s, starting at t=4 s. |
heavy_pressure | Constant gauntlet | 5× gunner_common on cardinals every 12 s, starting at t=2 s. So the player never gets a clean window on the boss. |
beacon_clearers | Anchor-clearing interrupters | 2× charger_common opposite the player every 15 s, starting at t=10 s. Fast melee that punish you for ignoring them. |
storm | Burst panic | 8× gunner_common on a ring every 20 s, starting at t=15 s. Long quiet windows, then a wall of mid adds. |
crescendo | Phase-tied escalation | t=0: 2 cardinal gunners → phase 1: 4 ring gunners → phase 2: 6 ring gunners. Add count climbs as the boss crosses HP thresholds. |
Wave structure
Every profile is { id, waves[] }. A wave is:
{
trigger, // when it fires
enemyTypeId, // what to spawn
count, // how many
position, // where, relative to arena
affixIds?, // optional affix stamp on each spawn
abilityIds?, // optional abilities granted to each spawn
}
Triggers
Three trigger kinds, all evaluated per wave per tick:
time—{ kind: 'time', at }. Fires once whenbossEncounterTime >= at. Tracked in a one-shot set.recurring—{ kind: 'recurring', every, from?, until? }. Fires everyeveryseconds inside the optional[from, until]window. The runtime computes how many fires are due withfloor((now - from) / every) + 1but only steps one fire per tick, spreading spawn cost across frames if a long pause caused many fires to come due simultaneously.phase—{ kind: 'phase', index }. Fires once when anysharesHealthWithBossenemy in the world reportsphaseIndex === index. Phase index is stamped on the host by therespawn_asaffix when the boss crosses an HP threshold.
Phase index convention
phaseIndex starts at 0 for the spawn body — so the initial boss host is
phase 0. Crossing thresholds advances the index: a boss declaring two
thresholds (e.g. Awakened Mech at 0.66 and 0.33) yields phase indices 1 and
2. A crescendo wave keyed to { kind: 'phase', index: 1 } therefore fires
on the first threshold, not at encounter start.
Position strategies
All positions route through arena helpers so pressure adds cannot spawn outside the active encounter footprint (spec §2.6 / §3.4 “spawn safety”):
| Position | Where |
|---|---|
cardinal | Cardinal pads at 0.7× arena radius. Wraps if count exceeds 4. |
ring | Evenly-spaced ring of N points at 0.7× arena radius. |
random | N independent random points inside the arena. |
edge_random | Evenly-spaced perimeter points rotated by a random offset, so successive waves don’t land on the same spots. |
opposite_player | Mirrored across arena center at 0.85× radius, clustered with 30–60 px angular jitter. |
Affixes and abilities
affixIds and abilityIds are optional per wave. When present, each spawned
enemy is stamped with enemy.affixes = [{ defId, state: {} }] and
enemy.abilities = [{ defId, cooldownRemaining: 0, state: {} }] after spawn,
on top of whatever the base enemy type already carries.
Key facts
- Data:
src/starship-survivors/data/spawn-profiles.ts— six profiles, all enemy ids reference the standard registry. - Runtime:
src/starship-survivors/engine/enemies/boss-spawn-profile.ts— per-wave id is<profileId>#<index>; module state is reset at encounter boundaries. - Pressure adds never share health with the boss.
- Phase index 0 is the body; phase wave triggers fire on indices ≥ 1.
- Recurring waves only step one fire per tick to spread cost.
- Unknown profile id is a crash, not a no-op.