The pack
runProgression
function runProgression():Pack<{runProgression:typeof rules}>
Install renderer-independent run progression policies.
Helpers
availableUpgrades
function availableUpgrades(defs: readonly Upgrade[], levels: Readonly<Record<string, number>>): Upgrade[]
The upgrades that can be taken at these levels: below their max level, with prerequisites met and not replaced by one already held.
createRun
function createRun(seed = 0, difficulty: Difficulty = 'medium', definitions: readonly Upgrade[] = actionUpgrades, profile?: Profile)
Start a run with a seed (default 0), difficulty (default medium), upgrade definitions (default actionUpgrades) and an optional profile. Throws on invalid definitions or seed.
draftUpgrades
function draftUpgrades(defs: readonly Upgrade[], levels: Readonly<Record<string, number>>, seed: number, count = 3): string[]
Stable without replacement draft. Evolution definitions use requirements and replaced weapon IDs.
generateRunMap
function generateRunMap(seed: number, difficulty: Difficulty = 'medium'): RunMap
Generate varied, reachable DAGs; every offered route reaches a boss and every node is reachable.
Classes
RunProgression
class RunProgression
A branching run: a seeded map of encounters, reward drafts, shops, upgrades and a persistent profile.
readonly definitions: Upgrade[]— The upgrade definitions, a copy of those passed in.state: RunState— The run's plain-data state;snapshot()copies it.constructor(seed = 0, difficulty: Difficulty = 'medium', definitions: readonly Upgrade[] = actionUpgrades, profile: Profile = { unlocks: [], completedRuns: 0 })node()— The node the run is at.finish(reward = 10)— Finish the current encounter: payrewardcredits (default 10) and offer a draft, or win the run at the boss. Returns false when it was already finished or the run is over. Throws unlessrewardis a whole number, 0 or more.offer()— The draft on offer: up to 3 upgrade ids. One is made if none is pending, then the same ids are returned until one is chosen.choose(id: string)— Take an offered upgrade and clear the draft. Returns false whenidis not on offer.buy(id: string)— Buy an upgrade at a shop for its price. Returns false unless the run is at a shop with credits enough, the upgrade is available and not already bought there, and the run is not over.travel(id: string)— Move to the connected nodeid. Returns false unless this encounter is finished, no draft is pending,idis one of the node'snextand the run is not over.fail()— End the run as lost, and drop any draft on offer. Does nothing once the run is won.restart()— Start a new run on the same seed and difficulty, keeping the profile.newRun(seed: number, difficulty = this.state.map.difficulty)— Start a new run onseed(difficulty defaults to the current one), keeping the profile and the definitions. Throws unless the seed is an unsigned 32-bit whole number.snapshot()— A plain copy of the run state.restore(s: RunState)— Load a snapshot. Throws when its version is not 1, its map does not match its seed and difficulty, it names an unknown node or upgrade, a level is out of range, a number is not a whole number or a draft entry is not available.
Constants
actionUpgrades
const actionUpgrades: Upgrade[]
The built-in upgrade set: bolt, haste, multishot, vitality and magnet, and Star nova, which evolves from bolt 3 with haste 1 and replaces bolt.
Types
Profile
interface Profile
What outlives a run: carried into the next by restart and newRun.
unlocks: string[]— Ids of unlocks earned;veteranis added on the first boss win.completedRuns: number— Runs won so far.
RunMap
interface RunMap
A generated run map: a graph of encounters, column by column, from a start to a boss.
seed: number— The seed the map was generated from; the same seed and difficulty give the same map.difficulty: Difficulty— The difficulty the map was generated for; harder maps are deeper.nodes: RunNode[]— Every node, in column order.start: string— Id of the first node.
RunNode
interface RunNode
One encounter on the run map.
id: string— Unique id of the formdepth:lane.depth: number— Column of the node along the run, 0 at the start; the last column is the boss.lane: number— Row of the node within its column, 0 at the top.kind: 'fight' | 'elite' | 'rest' | 'shop' | 'boss'— What the node is: a fight, an elite fight, a rest, a shop or the boss.next: string[]— Ids of the nodes the run can travel to from here, in the next column.seed: number— Unsigned 32-bit seed for the game to build this node's encounter from.
RunState
interface RunState
The whole run as plain data, for saving.
version: 1— The snapshot format; always 1.map: RunMap— The map the run is on.current: string— Id of the node the run is at.cleared: string[]— Ids of the nodes whose encounters have been finished.credits: number— Credits held, from finished encounters; shops spend them.upgrades: Record<string, number>— Level held of each upgrade, by id; upgrades not held are absent.draft: string[]— Ids of the upgrades on offer now; empty when none is pending.draftIndex: number— How many drafts have been offered; it varies the seed of the next one.shopBought: string[]— Purchases made, asnodeId/upgradeId, so a shop sells each upgrade once.won: boolean— Whether the boss has been cleared.lost: boolean— Whether the run has been lost.profile: Profile— The persistent profile carried between runs.
Upgrade
interface Upgrade
An upgrade a run can offer or sell.
id: string— Unique, non-empty id.name: string— The name shown to the player.maxLevel: number— Highest level it can reach; a whole number, 1 or more.price: number— Price in credits at a shop; a whole number, 0 or more.requires: Record<string, number>— Prerequisites as upgrade id to the minimum level that must be held first.replaces: string[]— Ids of upgrades removed when this one is taken (an evolution replaces its base weapon), which can then not be offered again.


