Assemble Run Pipeline

assembleRunDef is the single canonical assembly path that merges every metagame selection into one RunDefinition — the engine’s run-config contract. The engine itself does not know where any field came from; everything funnels through this function.

Inputs

AssembleParams carries the player’s selections from the hub:

  • shipId — hull class to fly (v4 bare hull name, e.g. 'Bulwark'). Star is derived from inventory XP via useInventoryStore.currentStar(shipId), defaulting to 1.
  • node — optional partial overrides for the mission/node config, merged onto DEFAULT_RUN.node.
  • unlockPools — progression-gated content pools from deriveUnlockedContent(). When omitted, no gating is applied.
  • planetIndex — index into PLANETS. Drives biome, level preset, and enemy-count multiplier.
  • isChallenge — Challenge Mode flag (10-level run, 2× rewards, 1.5× enemy difficulty).

Merge order

The pipeline assembles these contributions in order:

  1. Ship specgetShipDef(shipId, star) resolves the rarity-banded hull. Direct stats convert to engine combat stats via toShipCombatStats() and meta stats via toShipMetaStats().
  2. Node overridesparams.node partial spreads onto DEFAULT_RUN.node.
  3. Planet presetPLANETS[planetIndex] supplies biome and levelPreset (resolved through LEVEL_PRESETS) when the caller did not override them on node.
  4. World knob multipliers — base worldKnobs are multiplied by:
    • planet.enemyCountMult (per-planet, e.g. Voidstar = 2×)
    • rarityScale from RARITY_SCALE[shipDef.rarity] (common 0.5 → legendary 1.0) — applied to enemy count, HP, and damage so the whole run scales with hull rarity
    • challengeDiffMult (1.5× on Challenge Mode) — applied to count, HP, and damage
    • challengeRewardMult (2.0× on Challenge Mode) — applied to rewardMult
    • rarityScale is also stored on worldKnobs so hardcoded melee/charger damage call sites can read it.
  5. Facilities + supply — buildings are disabled; facilities stays at DEFAULT_RUN.context.facilities zeros and supplyLevel = 0.
  6. Codex / artifact context — content gating fields (weaponPool, upgradePool, levelupRarityCap, weaponCacheRarityCap) are all null (no gating). eventsUnlocked, starsUnlocked, and eventTier = 3 are forced on. startingArtifactId is always null (Tick 67 removed the starting-artifact grant).
  7. IdentityplanetId and isChallenge written through to context.

Output

A complete RunDefinition (version: 2) with:

  • node — merged node config (biome + level preset from planet, plus overrides).
  • ship — id, color, accent, combatStats, metaStats, weaponSlotCount, nonWeaponSlotCount, startingWeapons (copied).
  • context — gated/forced content flags, facilities, planetId, isChallenge, supplyLevel, worldKnobs, startingArtifactId.
  • spawn, player, timing — passthrough from DEFAULT_RUN.

Invariant

There is exactly one assembly path. The engine’s run loop never reads from the hub, inventory store, or planet table directly — it only reads RunDefinition. Any new metagame input (new ship dimension, new planet field, new unlock) must be folded into assembleRunDef before it can influence a run.

Throws if shipId is not found in the roster.