data/modifiers.ts

Level-up upgrade pool. Defines the ModifierTypeDef / ModifierEffect interfaces, the per-rank scaling helper getModifierValue, the 19 entries in MODIFIER_TYPES, and lookup tables (MODIFIER_TYPE_MAP, MODIFIER_RARITY_COLORS).

⠀ Στυξ Purpose

Single source of truth for the bundled and standalone horizontals offered as level-up rewards. Consumers: engine/weapons/weapons.ts (reads upgradeCounts for damage_* tags), engine/world/leveling.ts (offers horizontals as level-up cards), engine/combat/damage.ts (applies damageReduction). Header comment names these three consumers explicitly.

⠀ Στυξ Types

ModifierTypeDef

FieldTypeNotes
idstringStable key used as map index and for upgradeCounts lookups
namestringUI label
descriptionstringReward-card text
iconstringEmoji or short string (e.g. 'XP')
maxLevelnumberAll 19 entries use 20
rarity'common' | 'uncommon' | 'rare'Drives roll weights via leveling.ts
category'offense' | 'defense' | 'utility'UI grouping
effectsModifierEffect[]One or more stat deltas

ModifierEffect

FieldTypeNotes
statstringTarget stat key (e.g. hpMax, damage, magnetRange)
mode'flat' | 'percent'Percent → mult = 1 + sum(base * H) when k = -1
basenumberPer-rank value (percent: 0.10 = +10%)
perLevelnumberLEGACY — set to 0 for new horizontals
knumberScaling mode selector — see below

⠀ Στυξ Scaling — getModifierValue(effect, level)

Four modes selected by k:

kModeFormulaUse
-1Flat per pickbaseEvery entry below uses this — identical bonus each rank
0Front-loaded harmonicbase * 5 / levelBig first pick, diminishing
1Linearbase * levelFlat growth per pick, no front-loading
> 1Legacy diminishingbase + perLevel * level / (level + k)Pre-v3.26 horizontals only

getModifierDescription(def, level) formats each effect as ${stat with underscores → spaces} +${val * 100}% (percent) or +${val.toFixed(val < 1 ? 3 : 0)} (flat).

⠀ Στυξ Modifier Table (19 entries)

All entries have maxLevel: 20, perLevel: 0, k: -1.

Defense

idNameRarityEffects (per rank)
healthHealthcommonhpMax +15%, damageReduction +0.75 flat, hpRegen +0.5 flat
shieldShielduncommonshieldMax +15%, shieldRegenRate +7.5%, shieldRegenDelay -4.5%
vitalityVitalityuncommonhpRegen +1.0 flat
bulwarkBulwarkuncommondamageReduction +2.0 flat
chargeChargeuncommonshieldRegenRate +12%
overshieldOvershielduncommonshieldMax +25%

Offense

idNameRarityEffects (per rank)
damage_allAll DMGcommondamage +10% (universal)
damage_bulletBullet DMGcommondamage +25% (bullet-tag)
damage_energyEnergy DMGcommondamage +25% (energy-tag)
damage_fireFire DMGcommondamage +25% (fire-tag)
damage_bombBomb DMGcommondamage +25% (bomb-tag)
leechLeechrarelifeSteal +0.005 flat (+10% at L20)

Utility

idNameRarityEffects (per rank)
speedSpeedcommonthrust +30%, drag -10%, maxSpeed +30%
heatHeatuncommonheatBurnRate -7.5%
magnetMagnetcommonmagnetRange +40 flat
xp_gainXP GaincommonxpGainMult +0.04 flat (+80% additive at L20)
luckLuckcommonluckMult +0.10 flat (+200% additive at L20)
thrustThrustuncommonthrust +50%

Damage-tag distinction

damage_bullet / damage_energy / damage_fire / damage_bomb share a stat: 'damage' but the id is what engine/weapons/weapons.ts reads from upgradeCounts to gate each one to its weapon tag. damage_all applies regardless of tag.

⠀ Στυξ Bundle vs. Standalone Pattern

Several uncommon/rare entries are deliberate standalones of single stats from the common bundles, at higher per-rank rates so dedicated builds can stack faster:

BundleStandaloneRatio
Shield shieldMax +15%Overshield +25%1.67×
Shield shieldRegenRate +7.5%Charge +12%1.6×
Health damageReduction +0.75Bulwark +2.02.67×
Health hpRegen +0.5Vitality +1.02.0×
Speed thrust +30%Thrust +50%1.67×
(none — net new)Leech +0.5% lifeStealn/a

Uncommon rarity gates the standalones behind the common bundles. Leech is rare because lifesteal had no level-up path before tick 59.

⠀ Στυξ Drag-Floor Detail (Speed)

Speed drag -10% per rank reaches multiplier 0 at rank 10. core/modifiers.ts clamps multipliers via Math.max(0, 1 + sumPct) so ship.drag floors at 0 (no coast resistance) but never goes negative. Thrust still drives toward the (now much higher) maxSpeed cap. See inline comment lines 152-159.

⠀ Στυξ Lookup Tables

export const MODIFIER_TYPE_MAP: Record<string, ModifierTypeDef>
export const MODIFIER_RARITY_COLORS: Record<string, string>

MODIFIER_TYPE_MAP is built once at module load from MODIFIER_TYPES.

MODIFIER_RARITY_COLORS:

RarityHex
common#44aa44
uncommon#4488ff
rare#0070dd

No epic / legendary keys defined here — leveling.ts references higher tiers separately via luckMult rarity weights but the color map stops at rare.

⠀ Στυξ Consumer Touchpoints

FileReads
engine/weapons/weapons.tsupgradeCounts['damage_bullet' | 'damage_energy' | 'damage_fire' | 'damage_bomb']
engine/world/leveling.tsFull MODIFIER_TYPES list; _rollRarity consumes ship.luckMult
engine/combat/damage.tsship.damageReduction (subtractive); line 363 caps lifeSteal heal at min(hpMax, hp + amt)
core/modifiers.tsPer-stat multiplier aggregation, Math.max(0, 1 + sumPct) clamp
engine/core/state.tsBase stat values (xpGainMult starts at 1.15)

⠀ Στυξ EXTRACT-CANDIDATE

  • The bundle-vs-standalone ratios (1.67× / 1.6× / 2.67× / 2.0×) are an implicit design pattern not captured anywhere except inline comments — candidate for a design wiki page on horizontal-tier scaling.
  • k = 0 and k = 1 and k > 1 modes exist in getModifierValue but no current entry uses them — legacy / unreferenced code path, candidate for removal if no future horizontals revive harmonic / linear scaling.
  • perLevel field is documented as LEGACY and set to 0 on every entry — candidate to drop from ModifierEffect after confirming no external readers.
  • MODIFIER_RARITY_COLORS omits epic / legendary though leveling.ts rolls those tiers — candidate to either extend the color map or document the truncation.
  • The damage_* family duplicates stat: 'damage' four times and depends on id for tag-gating in weapons.ts — candidate for an explicit tag field on ModifierEffect to avoid the id-as-implicit-tag coupling.