Weapon Stat Curves
How a weapon’s per-level stats are resolved from level 1 to 20. Source: src/starship-survivors/data/weapons/_helpers.ts and src/starship-survivors/data/weapons/_types.ts.
The WeaponStat shape
Every level-scaling weapon stat is declared as:
interface WeaponStat {
base: number; // value at level 1
scaling: number; // value added per level beyond 1
}Stats authored this way include damage, fireRate, acquireRange, projectileSpeed, projectileSize, blastRadius, beamWidth, chainRadius, homingStrength, returnSpeed, lingerSec, orbitRadius, triggerRadius, ringRadius, ringBurstOutDist, beamDecayBlastRadius, artilleryStrikeRadius, scatterRadius, pullRadius, pullForce, splashRadius, bladeSize, orbitSpeed. (See WeaponCoreSpec in _types.ts.)
Resolution at a level
A WeaponStat is evaluated via getWeaponStatAtLevel(stat, level):
value = stat.base + stat.scaling * (level - 1)Linear in level. Level 1 returns base; every level after adds scaling once. This is the only resolution function for WeaponStat — no curve is applied here.
Curve remapping (optional, weapon-wide)
A weapon may declare scalingCurve?: ScalingCurve on its WeaponCoreSpec to remap the effective level before stats are read. getEffectiveLevel(level, curve) normalizes the input level into t = (level - 1) / 19, applies the curve, then maps back to 1 + curvedT * 19. The four curves shipped today (per _helpers.ts applyCurve) are:
linear—tunchanged. Default. Also returned for'none'/missing curve.exponential—t^2.5. Slow start, sharp ramp at high levels.steep_exp—t^3. Even slower start, even steeper top.front_loaded—t^0.4. Big early-level gains, flattens at high levels.s_curve—3t² - 2t³. Smoothstep; slow at both ends, fast in the middle.linear_fast—0.15 + 0.85t. Linear but starts at +15% baseline.
The remapped effective level is then fed into getWeaponStatAtLevel. See scaling-curves for the canonical curve catalog.
Stepped stats (bracketed values)
Some stats step at level breakpoints rather than scaling continuously. These use:
type SteppedStat = [minLevel: number, value: number][];Read via getSteppedStatAtLevel(steps, level) — walks the array and keeps the last entry whose minLevel <= level (floor lookup on a sorted ascending list). Used for projectileCount, chainCount, burstPattern, maxActive, starCount, beamDecayExplosions, shellCount, emberCount, bladeCount, seekerCount, pelletCount.
For fractional levels (mid-level interpolation), getProbabilisticSteppedStat flips a random roll weighted by the fractional part to choose between the floor and ceil bracket values.
Damage is special
Damage gets an additional multiplier on top of its WeaponStat resolution. getWeaponDamageMult(level) interpolates piecewise-linearly through anchors [1, 1.00], [4, 1.25], [8, 2.00], [12, 3.00], [16, 5.00], [20, 7.50] and layers a low-level buff (+30% at L1, decaying 5%/level, gone by L7). See damage-multiplier-curve. Legendary weapons use the level-20 multiplier (LEGENDARY_DAMAGE_BASELINE_MULT = 7.5) as their baseline so a legendary at L1 hits like a normal L20.
Related
- scaling-curves — curve catalog and shape intuition
- stepped-stats — how level-bracketed stats are authored
- damage-multiplier-curve — the damage-specific overlay
- vfx-tier-thresholds —
getVfxTier(level)thresholds at L4/L8/L14