data/run-config.ts — RunDefinition schema

The single typed contract the engine consumes to boot a run. The metagame assembles a RunDefinition from player loadout, mission selection, ship choice, facility bonuses, codex bonuses, and world knobs; the engine reads it without knowing where any value came from. No field is optional (except the few explicitly marked ?). The engine throws on missing or invalid fields via validateRunDef.

Schema version is 2. Header comment notes this matches archive DEFAULT_RUN_DEF v4 structure with three adaptations: heat-system fields kept for data completeness but not consumed by engine; durability removed; audio removed.

Per-run kill threshold

EVENT_KILL_THRESHOLD = 25

Kill count required before sub-events (charge, etc.) start spawning during a run. Not a progression unlock — resets every run.

Top-level shape

RunDefinition is a flat object with seven top-level keys (plus version and the optional sandbox).

KeyPurpose
versionSchema version. Current = 2. Validator throws on mismatch.
nodeMission identity + world configuration (WHAT and WHERE).
shipVessel the player is flying — combat stats, slot counts, starting weapons.
contextMetagame state read by engine but never written (facilities, codex, world knobs, bonuses).
spawnSpawn timing, quantity curve, quality curve.
playerPlayer input tuning.
sandbox?Optional. Disables timer, director, leveling. Used by /sandbox playground.
timingDeath timer, hit-flash decays.

Sub-interfaces

MissionObjective

FieldTypeNotes
typestring`‘explore'
countnumberBeacons/items/kills required. 0 = N/A for survive_timer.

BossConfig

FieldTypeNotes
idstringBoss enemy type ID. Empty string = no boss.
baseMultnumberBase stat multiplier applied to boss template.
penaltyPerMissnumberScore penalty per missed objective (0–1).

VisionConfig

FieldTypeNotes
baseRadiusnumberLit radius around ship (world units). Default 1020.
dimWidthnumberWidth of dim-to-dark gradient ring.
darkOpacitynumberDark-zone opacity (0 = transparent, 1 = pitch black).
ambientLightnumberAmbient floor for dark zone (0–1).

EventPoolConfig

Single field pool: string[] — event type IDs that can spawn during this mission. Empty pool triggers bridge.ts buildDefaultEventPool() (default mix 45 / 26.7 / 16.7 / 6.7 / 5).

ShipCombatStats

Everything the engine uses for ship physics and damage. Grouped into five sub-blocks.

Core combat

FieldNotes
hpMax, hpRegenHull HP cap + per-second regen.
shieldMax, shieldRegenRate, shieldRegenDelay, shieldRegenFillTimeShield cap, regen rate, delay before regen starts, fill time.
damageReductionFlat DR.
thrust, maxSpeed, dragLocomotion.
weaponDamagePct, fireRatePctWeapon bonuses (percent, 0 = no bonus).
luck, magnetRange, currencyBonusDrop quality, pickup range, currency bonus.
turnSpeedRadians per frame at 60fps.

Heat / overheat

FieldNotes
overheatBurnDamage per second when overheated.
overheatCoolCooling rate (units per second).
heatSpeedTargetSpeed at which heat stops accumulating.
heatBoostMultSpeed multiplier when boosting.
heatCurve`‘linear'
burnoutSeverityBurnout damage scale. 0.35 = mild, 1.0 = standard, 2.0 = devastating.

Physics / collision (per-ship tunable)

FieldNotes
shipScaleVisual + collision scale multiplier.
ramSpeedBleedSpeed fraction retained after high-speed ram.
contactSpeedBleedSpeed fraction retained on low-speed enemy contact.
terrainRestitutionBounce coefficient off terrain (1.09 = 9% reflect).
terrainFrictionTangential friction on terrain bounce (0 = ice, 1 = velcro).
ramThresholdMinimum speed (u/s) to deal ram damage.
ramDamageLo, ramDamageHiRam damage at threshold and at max ram speed.
pushRatioShip’s share of push-apart on overlap.
enemySolidityHow much overlap push goes to ship vs enemy.
contactDecelSpeed multiplier on low-speed enemy contact.
contactCooldownSeconds of invuln between body contacts.
meleeMult?Ramming damage multiplier (0.1×–3.0×, default 1.0).
shipClass?`‘heavy'

Visual / sprite

FieldNotes
spriteHueHue rotation (−180 to 180).
spriteSaturation, spriteContrast, spriteBrightnessStandard image filters.

Vehicle feel

FieldNotes
rotatestrue = ship faces input direction. false = hovercraft (fixed angle, omnidirectional thrust).
fixedAngleDegFixed facing angle when rotates === false (0=up, 90=right). Ignored when rotates === true.
accelCurve`‘linear'
dragCurve`‘linear'
stopShakeIntensityCamera shake on hard stop.
heatShakeIntensityContinuous camera shake while heat above threshold.
heatShakeThresholdHeat fraction (0–1) above which heat-shake kicks in.
stopSpringAmtSpring-back amount when stopping.

ShipMetaStats

Non-combat modifiers that stack with combat-side equivalents.

FieldNotes
luckStacks with combatStats.luck.
currencyBonusStacks with combatStats.currencyBonus.
objectiveSpeedObjective completion speed bonus (percent).

StartingWeapon

{ id: string; level: number }. Ship must have at least one (validator throws on empty array).

FacilityBonuses

Additive percentages from metagame buildings unless noted. Zero = no bonus. Negative values are valid for time reductions.

Legacy (kept for compat): weaponDamagePct, upgradeChoicesBonus, dropRarityBonus, xpBonusPct, objectiveBonusPct.

Current building bonuses:

FieldSource buildingEffect
arcadeCreditsPct+% credits from arcade runs
missionCreditsPctYard+% credits from missions
buildTimePct, moveTimePct, upgradeTimePct, gemSkipCostPctRefinery−% construction / move / upgrade time, −% gem skip cost
worldEventSpawnPctArray / Foundry / Beacon+% world-event spawn rate
chestRewardPct, challengeXpPctArchive+% chapter chest rewards, +% challenge XP
missionSuccessChancePct, missionBoardSlots, missionShipSlots, missionRewardPctRelayMission board mechanics
missionDurationPctPort−% mission duration
shipDropChancePctYard / Port+% ship drop chance
eventRewardQualityPctBeacon+% event reward quality
hullRolePackagePct, hullMissionContributionPctHangarPer-hull package/role bonuses
weaponDamageBonusPctLab+% damage for assigned weapon
enemyCountPctBarracks+% enemy count & density
eliteRarityCapBarracks`‘rare'

CodexBonuses

FieldTypeNotes
damageVsRecord<string, number>Damage multipliers vs enemy types, e.g. { 'scout': 0.1, 'juggernaut': 0.15 }.
weaponBonusesRecord<string, number>Per-weapon bonuses, e.g. { 'rifle': 0.05 }.

WorldKnobs

Multipliers on enemy stats and rewards.

FieldNotes
enemyHpMult, enemyDamageMult, enemySpeedMult, enemyCountMultDirect multipliers.
rewardMultRewards multiplier.
rarityScaleHidden ship-rarity difficulty scale. 1.0 = legendary baseline, lower = easier run. Set at run-assembly from player’s ship rarity. Already baked into enemyCountMult / HpMult / DamageMult above; also applied separately at hardcoded melee/charger damage call sites (which bypass enemy.damageMult).

SessionBonuses

FieldNotes
dailyBonusActiveDaily login bonus flag.
rareSignalRewardMultReward multiplier for rare-signal runs.
deathDefianceTokensToken count.

Top-level fields

node

FieldNotes
idUnique node/mission ID for tracking.
seedWorld-gen seed. 0 = auto-generate.
heatDifficulty heat level (1–10). Validator enforces range.
missionType`‘explore'
missionIdSpecific mission definition ID (scripted missions).
biomeBiome identifier for world generation.
timerSecondsMission timer (0 = untimed).
objectiveMissionObjective.
bossBossConfig.
visionVisionConfig.
eventsEventPoolConfig.
isRareSignalBonus-rewards flag.
levelConfig?LevelConfig from ./level-config. If omitted, engine uses DEFAULT_LEVEL_CONFIG.
weaponBoxCountWeapon boxes spawned in the world.
artifactBoxCountArtifact boxes spawned per level.
bossDefId?Override boss. When set, this BossDef.id spawns at end-of-level instead of the level-based default. Used by playground / dev scenarios. Empty/undefined → engine resolves from _currentLevel via LEVEL_BOSS_ROTATION in engine/bridge.ts.

ship

FieldNotes
idShip type ID, e.g. 'green_dart', 'blue_bigrig'.
color, accentHex color strings.
combatStatsFull ShipCombatStats — applied to ShipState at mission start.
metaStatsShipMetaStats.
weaponSlotCountValidator requires ≥1.
nonWeaponSlotCountModule/artifact slot count.
startingWeaponsNon-empty StartingWeapon[].

context

Read-only from the engine’s perspective. Mostly bonus pools and unlock state.

FieldNotes
accountLevelValidator requires ≥1.
weaponPool, upgradePool, artifactPool`string[]
startingArtifactIdPlayer-chosen starting artifact. Granted at run start at tier 0 (uncommon). Null = no starting artifact.
levelupRarityCap, weaponCacheRarityCapMax rarity tier for level-up and weapon cache pools.
eventsUnlocked, starsUnlockedBoth gate on lifetime kills ≥ 25.
eventTier0=locked, 1=basic, 2=mid, 3=high.
planetIdPlanet ID (matches display name, e.g. 3, 12, 21). Used for nebula archetype selection. 0 = random biome archetype.
isChallenge?Challenge Mode — 10-level run, same per-level difficulty curve as normal mode. Unlocked per planet after clearing normal-mode final boss. Doubled rewardMult is applied at run-assembly time.
supplyLevelSupply building level — controls XP crate count at run start (0 = none).
facilitiesFacilityBonuses.
codexCodexBonuses.
worldKnobsWorldKnobs.
bonusesSessionBonuses.

spawn

Quantity controls how many enemies are on screen. Quality controls what enemies spawn (tier selection). Both use tent-pole keyframes { time: seconds, value: 0–1 }; engine lerps between keyframes.

FieldNotes
initialDelaySeconds before first enemy.
spawnIntervalBase seconds between waves.
spawnIntervalVarianceRandom variance added to spawnInterval (0 = fixed).
batchSizeMax enemies per wave.
quantityCurve?Tent-poles. Default ramp: 0→0 at 0s, 0→0.3 at 30s, 0.3→1.0 at 120s, flat 1.0 after.
qualityCurve?Tent-poles. Default: continuous linear ramp 0→1 over full run duration.
curveRandomization?±randomization range within each tent-pole (0–1). 0 = deterministic, 0.2 = ±20%. Default 0.15.

player

FieldNotes
invulnDurationSpawn invuln seconds.
autoAimRangeAuto-aim detection radius (world units).
deadzoneJoystick deadzone (0–1 normalized magnitude).

sandbox?

Optional boolean. Disables timer, director, and leveling. Used by /sandbox playground. Defaults to false.

timing

FieldNotes
deathTimerSeconds on death screen before results.
enemyFlashDecayHit-flash decay rate (per second, multiplied by dt).
damageFlashDecayScreen damage-flash decay rate.

validateRunDef(def)

Throws on any missing or invalid field. Called once before passing to createMission().

Hard checks (all throw):

  • def truthy; version === 2.
  • node.id non-empty string; seed is number; heat ∈ [1,10]; missionType and biome non-empty strings; timerSeconds >= 0; objective.type and boss.id are strings; vision.baseRadius > 0.
  • ship.id non-empty; startingWeapons non-empty array.
  • Combat stats: hpMax > 0, shieldMax >= 0, thrust > 0, maxSpeed > 0, drag > 0, magnetRange >= 0, weaponSlotCount >= 1.
  • context.accountLevel >= 1; worldKnobs.enemyHpMult > 0, enemyCountMult > 0, rewardMult > 0.
  • spawn.initialDelay >= 0, spawnInterval > 0, batchSize >= 1.
  • player.invulnDuration >= 0, autoAimRange > 0, deadzone ∈ [0,1].
  • timing.deathTimer > 0, enemyFlashDecay > 0, damageFlashDecay > 0.

DEFAULT_RUN

Reference default. Used for quick-play and as the assembly template for the metagame. Values match archive DEFAULT_RUN_DEF v4 where applicable.

BlockNotable defaults
nodeid='quickplay', seed=0, heat=5, missionType='explore', biome='landing_site', timerSeconds=240 (4 min/level), objective survive_timer/0, boss { id:'', baseMult:1.0, penaltyPerMiss:0.3 }, vision { 1020 / 150 / 0.99 / 0.03 }, empty event pool (bridge fills default), weaponBoxCount=0, artifactBoxCount=1 (bridge applies 5% spawn chance — 20× rarer than always).
shipid='Backwater_Lizard', color #ffffff / accent #333333. Combat stats: hp 80, shield 10, thrust 50, maxSpeed 100, drag 0.3, turnSpeed 0.32, magnetRange 128, weaponDamagePct 12, fireRatePct 6, luck 12, currencyBonus 8. Heat: linear curve, burnoutSeverity 1.0, heatBoostMult=2.5. Physics: shipScale 1.0, ramSpeedBleed 0.80, terrainRestitution 1.09, ramThreshold 150, ramDamageLo/Hi 40/1000. Vehicle: rotates=true, accelCurve='linear', dragCurve='exponential'. 3 weapon + 3 non-weapon slots. Starting weapon rifle@1.
contextaccountLevel 1, all pools null (use-all), eventsUnlocked=false, starsUnlocked=false, eventTier=0, planetId=0, supplyLevel=0. All facilities zeroed; eliteRarityCap=null. Codex empty. WorldKnobs { hp 0.5, dmg 0.75, speed 1, count 1.0, reward 1, rarityScale 1 }. Bonuses { daily=false, rareSignalMult=1, deathDefianceTokens=1 }.
spawninitialDelay=1.0, spawnInterval=0.8, spawnIntervalVariance=0.5, batchSize=5. Quantity curve [0→0, 30→0.3, 120→1.0, 9999→1.0]. Quality curve [0→0, 30→0.1, 120→0.5, 240→1.0, 9999→1.0]. curveRandomization=0.15.
playerinvuln 2.0s, autoAimRange 400, deadzone 0.08.
timingdeathTimer 3.0s, enemyFlashDecay 6, damageFlashDecay 3.

Cross-references

  • ./level-configLevelConfig referenced by node.levelConfig?.
  • engine/bridge.tsbuildDefaultEventPool() (called when node.events.pool is empty), LEVEL_BOSS_ROTATION (used when node.bossDefId is unset).
  • createMission() — caller; receives the validated RunDefinition.

EXTRACT-CANDIDATE

  • Heat-system fields in ShipCombatStats are kept in the schema for data completeness but not consumed by the engine per header comment (lines 12–13). Candidates for extraction to a separate HeatConfig interface (or removal) once the heat system’s status is decided: overheatBurn, overheatCool, heatSpeedTarget, heatBoostMult, heatCurve, burnoutSeverity, heatShakeIntensity, heatShakeThreshold. EVENT_KILL_THRESHOLD (line 22) also references the events unlock at lifetime-kills ≥ 25 that appears in context.eventsUnlocked / starsUnlocked — single source of truth candidate.
  • Legacy fields in FacilityBonuses (lines 190–199): weaponDamagePct, upgradeChoicesBonus, dropRarityBonus, xpBonusPct, objectiveBonusPct are explicitly marked legacy in comments and overlap with newer fields (weaponDamageBonusPct, objectiveBonusPct vs metaStats.objectiveSpeed, etc.). Candidate for deletion sweep once the metagame stops reading them.
  • durability and audio removed per header comment — confirm no stale references elsewhere in the codebase.
  • isChallenge? is optional but rewardMult doubling is described as happening “at run-assembly time” — verify the assembly site applies it consistently and consider promoting to required field with default false.
  • bossDefId? empty-string vs undefined — header says “Empty/undefined” both fall back to default; consider tightening to a single sentinel (drop the ? and require empty string for “no override”) for fewer branches in consumers.
  • weaponPool / upgradePool / artifactPool use null to mean “use all”, but levelupRarityCap / weaponCacheRarityCap also use null for “no cap” — convention is consistent but worth documenting in one place rather than re-explaining at each field.