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
| Field | Type | Notes |
|---|---|---|
id | string | Stable key used as map index and for upgradeCounts lookups |
name | string | UI label |
description | string | Reward-card text |
icon | string | Emoji or short string (e.g. 'XP') |
maxLevel | number | All 19 entries use 20 |
rarity | 'common' | 'uncommon' | 'rare' | Drives roll weights via leveling.ts |
category | 'offense' | 'defense' | 'utility' | UI grouping |
effects | ModifierEffect[] | One or more stat deltas |
ModifierEffect
| Field | Type | Notes |
|---|---|---|
stat | string | Target stat key (e.g. hpMax, damage, magnetRange) |
mode | 'flat' | 'percent' | Percent → mult = 1 + sum(base * H) when k = -1 |
base | number | Per-rank value (percent: 0.10 = +10%) |
perLevel | number | LEGACY — set to 0 for new horizontals |
k | number | Scaling mode selector — see below |
⠀ Στυξ Scaling — getModifierValue(effect, level)
Four modes selected by k:
k | Mode | Formula | Use |
|---|---|---|---|
-1 | Flat per pick | base | Every entry below uses this — identical bonus each rank |
0 | Front-loaded harmonic | base * 5 / level | Big first pick, diminishing |
1 | Linear | base * level | Flat growth per pick, no front-loading |
> 1 | Legacy diminishing | base + 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
| id | Name | Rarity | Effects (per rank) |
|---|---|---|---|
health | Health | common | hpMax +15%, damageReduction +0.75 flat, hpRegen +0.5 flat |
shield | Shield | uncommon | shieldMax +15%, shieldRegenRate +7.5%, shieldRegenDelay -4.5% |
vitality | Vitality | uncommon | hpRegen +1.0 flat |
bulwark | Bulwark | uncommon | damageReduction +2.0 flat |
charge | Charge | uncommon | shieldRegenRate +12% |
overshield | Overshield | uncommon | shieldMax +25% |
Offense
| id | Name | Rarity | Effects (per rank) |
|---|---|---|---|
damage_all | All DMG | common | damage +10% (universal) |
damage_bullet | Bullet DMG | common | damage +25% (bullet-tag) |
damage_energy | Energy DMG | common | damage +25% (energy-tag) |
damage_fire | Fire DMG | common | damage +25% (fire-tag) |
damage_bomb | Bomb DMG | common | damage +25% (bomb-tag) |
leech | Leech | rare | lifeSteal +0.005 flat (+10% at L20) |
Utility
| id | Name | Rarity | Effects (per rank) |
|---|---|---|---|
speed | Speed | common | thrust +30%, drag -10%, maxSpeed +30% |
heat | Heat | uncommon | heatBurnRate -7.5% |
magnet | Magnet | common | magnetRange +40 flat |
xp_gain | XP Gain | common | xpGainMult +0.04 flat (+80% additive at L20) |
luck | Luck | common | luckMult +0.10 flat (+200% additive at L20) |
thrust | Thrust | uncommon | thrust +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:
| Bundle | Standalone | Ratio |
|---|---|---|
Shield shieldMax +15% | Overshield +25% | 1.67× |
Shield shieldRegenRate +7.5% | Charge +12% | 1.6× |
Health damageReduction +0.75 | Bulwark +2.0 | 2.67× |
Health hpRegen +0.5 | Vitality +1.0 | 2.0× |
Speed thrust +30% | Thrust +50% | 1.67× |
| (none — net new) | Leech +0.5% lifeSteal | n/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:
| Rarity | Hex |
|---|---|
| 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
| File | Reads |
|---|---|
engine/weapons/weapons.ts | upgradeCounts['damage_bullet' | 'damage_energy' | 'damage_fire' | 'damage_bomb'] |
engine/world/leveling.ts | Full MODIFIER_TYPES list; _rollRarity consumes ship.luckMult |
engine/combat/damage.ts | ship.damageReduction (subtractive); line 363 caps lifeSteal heal at min(hpMax, hp + amt) |
core/modifiers.ts | Per-stat multiplier aggregation, Math.max(0, 1 + sumPct) clamp |
engine/core/state.ts | Base 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 = 0andk = 1andk > 1modes exist ingetModifierValuebut no current entry uses them — legacy / unreferenced code path, candidate for removal if no future horizontals revive harmonic / linear scaling.perLevelfield is documented as LEGACY and set to0on every entry — candidate to drop fromModifierEffectafter confirming no external readers.MODIFIER_RARITY_COLORSomitsepic/legendarythoughleveling.tsrolls those tiers — candidate to either extend the color map or document the truncation.- The
damage_*family duplicatesstat: 'damage'four times and depends onidfor tag-gating inweapons.ts— candidate for an explicittagfield onModifierEffectto avoid theid-as-implicit-tag coupling.