Spawn Arc / Velocity-Cone Bias
Enemies always spawn off-screen at a distance from the ship, with the spawn ring biased toward the player’s recent movement direction so the action stays ahead of motion rather than behind it.
Off-screen spawn band
Spawns are constrained to a ring around the ship:
- Minimum distance: 450px —
MIN_SPAWN_DIST = 450is enforced insidespawnEnemy(). Any candidate point closer than 450px is pushed outward along its bearing so nothing pops in on top of the player. Seeengine/enemies/spawner.tsline 929. - Early-trickle band: 400–550px — the first-60s “guaranteed close-range” trickle picks
400 + rand * 150. These are the cosy, just-off-screen spawns that anchor opening pressure. Line 550. - Directional base distance: 350 + speed lookahead —
_spawnDirectional()addsmin(750, speed × 1.5)to a 350px base, capped at 1100px so spawns always land inside the 1500px prune boundary. Per-pack centre then jitters by ±150px, so the realised band reaches roughly 400–1250px (slow ships) up to ~1250–1400px equivalent for fast ships near the cap. Lines 718–747.
The 1500px prune boundary outside this range matters: anything that drifts past it gets recycled, so spawning further out would just feed the pool. The cap is deliberate.
Velocity-cone bias
The direction the player is moving — not their facing — drives the spawn arc.
- Heading source: 5-second position-sample window. The spawner stores the ship’s
(x, y)every frame and uses the displacement from oldest sample → current position asmoveAngle. This gives the true average heading and is immune to momentary stick jitter or twitchy strafe. If displacement < 10px (player is essentially stationary), it falls back toship.angle. Lines 707–716. - 70 / 30 split. For each spawn pack,
Math.random() < 0.7selects “ahead” mode; the remaining 30% pick a fully random bearing around the player. Line 738. - Cone width: ±π/4 (45° each side, 90° total). “Ahead” spawns roll
moveAngle + (rand - 0.5) × π × 0.5. So most spawns appear inside a 90-degree wedge centred on the direction of motion. Line 741. - Pack coherence. Every enemy in a pack shares the same spawn angle and a pack centre, then individuals scatter 30–60px around that centre. So a single roll seeds 2–4 (and up to ~6 in late game) clustered enemies in roughly the same place rather than dispersing them around the ring.
The bias is the reason that holding a direction “carries” the player into fresh enemies — you’re not running away from spawns, you’re running into them. The 30% random fraction keeps you from being able to perfectly outpace pressure by sustained drifting.
Spawn-grace window
Each planet defines a spawnGraceSeconds value (data/planet-config.ts). The spawner reads PLANETS[world.planetId]?.spawnGraceSeconds ?? 1 at line 451.
- Default: 1 second. Most planets use a 1s warm-up where no spawns fire. After grace ends, the early-trickle phase begins.
- The Void (planet 3): 0 seconds.
spawnGraceSeconds: 0skips the warm-up entirely — full pressure from t=0. The grace branch (if (planetGrace === 0) { graceRamp = 1; }) short-circuits to maximum ramp on frame 1. - Grace ramp shape (when grace > 0):
- 0 →
planetGraces:graceRamp = 0(no spawns) planetGraces → 30s:graceRamp = 0.55(flat at 55% of normal pressure)- 30s → 60s: linear interpolation from 0.55 → 1.0
- 60s+: full pressure
- 0 →
So the “first N seconds” window is really three nested phases:
- Hard grace (0 → 1s typical): nothing spawns.
- Flat ramp (1 → 30s): close-range trickle only, ~55% pressure.
- Growing ramp (30 → 60s): pressure climbs to full while still in trickle mode.
After 60s, the directional pack-spawn system takes over from the close-range trickle, and the velocity-cone bias becomes the dominant placement model for the rest of the run.
Why it works
The velocity cone is what makes Starship Survivors feel like a bullet hell with thrust rather than a static survivor arena. If spawns were uniformly random around the ship:
- Drifting in any one direction would empty the screen ahead.
- 70% of the threat would be behind you and irrelevant.
- Fast ships would coast through dead space.
The cone makes motion produce engagement. The 30% random tail prevents the cheese of stationary or constant-direction play. The 5-second sample window prevents the cheese of high-frequency direction flicks “fooling” the spawner.
See also
- Director Pressure — the ramp + kill-cap model the spawner draws against.
- Enemy Spawn Zones — zone-based pool selection that runs on top of the directional roll.
- Boss Spawn Profile — closing-room boss spawning that bypasses the regular spawner gate.