World Knobs
World knobs are the per-run difficulty multipliers that scale enemy stats and reward output. They live on RunDefinition.context.worldKnobs (typed as WorldKnobs in data/run-config.ts) and are populated at run-assembly time so the engine never has to recompute difficulty mid-run — every spawner, damage call site, and reward grant just reads the already-baked number.
Fields
The WorldKnobs interface holds six multipliers:
enemyHpMult— scales enemy hit points. Default = 0.5.enemyDamageMult— scales damage dealt by enemies. Default = 0.75.enemySpeedMult— scales enemy movement speed. Default = 1.enemyCountMult— scales the number of enemies on screen (acts on quantity-curve output). Default = 1.0.rewardMult— scales reward output (currency, XP, etc.). Default = 1.rarityScale— hidden ship-rarity difficulty scale.1.0= legendary baseline; lower values = easier run. Already baked into the three enemy knobs above, but also exposed separately so hardcoded melee/charger damage call sites (which bypassenemy.damageMult) can read it.
Validation in validateRunDef() requires enemyHpMult, enemyCountMult, and rewardMult all > 0 — a run with zero or negative values throws at load time.
Composition (assembly time)
assembleRunDef() in services/assembleRunService.ts composes the final knobs from three independent inputs layered on top of the DEFAULT_RUN.context.worldKnobs baseline:
- Base —
DEFAULT_RUN.context.worldKnobs(the constants above). - Planet —
PLANETS[planetIndex].enemyCountMult(e.g. Voidstar = 2×). Applied only toenemyCountMult. - Rarity —
RARITY_SCALE[shipDef.rarity]:common 0.5 / uncommon 0.6 / rare 0.7 / epic 0.8 / legendary 1.0. Applied to count, HP, and damage. - Challenge — when
params.isChallenge === true, a1.5×difficulty bump is applied to count/HP/damage, and a2.0×reward bump is applied torewardMult(a Challenge run is 10 levels long, so the reward bump compensates for the doubled length while the difficulty bump makes it actually punch harder).
The composition is straight multiplication:
enemyCountMult = base × planet × rarity × challengeDiff
enemyHpMult = base × rarity × challengeDiff
enemyDamageMult = base × rarity × challengeDiff
rewardMult = base × challengeReward
rarityScale = rarity
enemySpeedMult = base (untouched at assembly time)
rarityScale is also stored on worldKnobs (not just baked into the three enemy knobs) so the hardcoded melee/charger damage paths — which read raw enemy template values rather than enemy.damageMult — can apply the same scaling.
Consumers
The engine reads context.worldKnobs but never writes it. Three subsystems consume it:
- Spawner —
enemyCountMultscales the quantity-curve output, controlling how many enemies the director tries to keep alive. - Damage —
enemyHpMultandenemyDamageMultscale enemy template stats;rarityScaleis applied at hardcoded melee/charger damage call sites that bypass the standardenemy.damageMult. - Rewards —
rewardMultscales currency/XP grants from kills, events, and end-of-level rewards.
Why a single struct
Two reasons. First, all difficulty inputs converge into one struct at run assembly so the engine has a single place to read from — no need to know about planet config, ship rarity, or Challenge Mode. Second, the metagame can compose the knobs from arbitrary sources (planet, rarity, Challenge, future modifiers) without changing the engine contract: the engine just multiplies whatever it’s handed.
Source
src/starship-survivors/data/run-config.ts—WorldKnobsinterface, defaults,validateRunDef().src/starship-survivors/services/assembleRunService.ts— composition logic (RARITY_SCALE, planetenemyCountMult, Challenge multipliers).