The pack
crowdRunner
function crowdRunner(): Pack<{ crowdRunner: CrowdRunnerApi }>
The crowd-runner pack: game.crowdRunner, Count Masters' rules. No options.
Helpers
applyGate
function applyGate(count: number, g: GateOp): number
A gate's effect on a count. Never below 0; division rounds down.
checkCrowdRunnerSave
function checkCrowdRunnerSave(save: unknown): string | null
Why a save cannot be restored, or null when it can: its course must be one createCrowdRunner would accept, the
state's enemy counts and gates must match that course, its numbers must be finite, the crowd and the enemies whole
numbers, and the random stream a real one. restore throws with this reason.
createCrowdAutopilot
function createCrowdAutopilot(game: CrowdRunnerGame, options: { lookahead?: number } = {}): CrowdAutopilot
The run's autopilot. It only reads game.
createCrowdRunner
function createCrowdRunner(options: CrowdRunnerOptions = {}): CrowdRunnerGame
Start a run. Throws, with every fault, on options validateCrowdRunner refuses.
gateDefaults
const gateDefaults: NonNullable<CourseOptions['ops']>
The gates a generated course offers by default, with their operand ranges and weights.
gateLabel
function gateLabel(g: GateOp): string
A gate as written on it: +10, x3, -5, ÷2.
planRunnerCourse
function planRunnerCourse(options: CourseOptions, rng: Rng): RunnerCourse
Generate a course.
validateCrowdRunner
function validateCrowdRunner(options: CrowdRunnerOptions = {}): string[]
Check a run's options. Empty when it can be played.
Types
CourseFinish
type CourseFinish = | { kind: 'stairs'; steps: number; perStep: number } | { kind: 'boss'; hp: number; hit: number; every: number }
How the course ends.
CourseOptions
interface CourseOptions
How a course is generated.
length?: number— Lane length to the finish. Default 320.width?: number— Lane width. Default 10.spacing?: number— Distance between features. Default 32.mix?: { gates?: number; obstacles?: number; enemies?: number }— Shares of gates, obstacles and enemy crowds among the features. Default 0.55, 0.25, 0.2.ops?: { op: GateOp['op']; n: [number, number]; weight?: number }[]— The gates on offer, by weight. Default +, x, and a little - and /.kinds?: { kind: Obstacle['kind']; weight?: number }[]— Obstacle kinds, by weight. Default saws, walls and pits.enemyShare?: [number, number]— Enemy crowds as a share of the count the best choices would give there. Default [0.25, 0.5].finish?: CourseFinish— The finish. Default a staircase of 20 steps, 4 members a step.start?: number— The count the crowd starts with, and the space between its members, for sizing enemies. Default 5 and 0.42.packing?: number— The space between members of a crowd, in lane units, for sizing enemies. Default 0.42.max?: number— The most members a crowd can have. Default 999.
CrowdAutopilot
interface CrowdAutopilot
A player that steers.
next(): number— Where across the lane to steer for now.
CrowdEvent
type CrowdEvent = | { kind: 'gate'; index: number; side: 'left' | 'right'; op: GateOp; before: number; after: number } | { kind: 'lost'; by: Obstacle['kind'] | 'enemy' | 'boss'; n: number } | { kind: 'clash'; index: number } | { kind: 'cleared'; index: number } | { kind: 'finish' } | { kind: 'step'; step: number } | { kind: 'won'; score: number } | { kind: 'over' }
Something that happened.
CrowdRunnerApi
interface CrowdRunnerApi
What the crowd-runner pack adds to a game: game.crowdRunner.
create: typeof createCrowdRunner— Start a run.validate: typeof validateCrowdRunner— Check a run's options.checkSave: typeof checkCrowdRunnerSave— Why a save cannot be restored, or null.plan: typeof planRunnerCourse— Generate a course by seed.apply: typeof applyGate— A gate's effect on a count.label: typeof gateLabel— A gate as written on it.gates: typeof gateDefaults— The built-in gate mix.autopilot: typeof createCrowdAutopilot— A player for an attract mode or a test.
CrowdRunnerGame
interface CrowdRunnerGame
A run.
readonly state: CrowdRunnerState— The live state; read it, do not change it.readonly course: RunnerCourse— The course being run.readonly options: Readonly<{ speed: number; steer: number; spacing: number; max: number }>— The speeds, spacing and crowd limit in force, with their defaults filled in.move(x: number): void— Steer: -1 left to 1 right, as a share of the steering speed.aim(x: number | null): void— Steer towards a place across the lane (as dragging does), or null to stop.step(dt: number): void— Advance, in fixed steps of 1/60 s.obstacleX(o: Obstacle, time: number): number— An obstacle's middle across the lane at a time.formation(count: number): { x: number; z: number }[]— The crowd's members around its middle, for any count.view(): CrowdView— Every member's position, for drawing, with a copy of the state.drain(): CrowdEvent[]— Takes the events since the last call, oldest first, and clears them. Only the latest 512 are kept.snapshot(): CrowdRunnerSave— The whole run as plain JSON, to save.restore(save: CrowdRunnerSave): void— Replaces the run with a saved one fromsnapshot()and discards pending events. The save carries its course, so it need not be this run's. Throwscrowd-runner.restore: …, changing nothing, on a savecheckCrowdRunnerSaverefuses.
CrowdRunnerOptions
interface CrowdRunnerOptions
A run's options.
seed?: number— Default 1.course?: RunnerCourse | CourseOptions— A course by hand, or how to generate one. Default generated with its defaults.start?: number— Members at the start. Default 5.speed?: number— Units a second up the lane. Default 8.steer?: number— Units a second side to side. Default 12.spacing?: number— Space between members. Default 0.42.max?: number— The most members a crowd can have. Default 999.clash?: number— Seconds a clash between equal crowds takes. Default 0.8.
CrowdRunnerSave
interface CrowdRunnerSave
A run saved as plain JSON.
state: CrowdRunnerState— The run's state.course: RunnerCourse— The course being run.aim: number | null— The place across the lane the crowd is steering towards, or null when steered bymove.carry: number— Seconds of unspent time towards the next 1/60 s step.hit: number— Seconds since the last step climbed or boss blow at the finish.rng: RngState— The random stream's state.
CrowdRunnerState
interface CrowdRunnerState
Where the run stands.
time: number— Seconds the run has lasted.phase: 'run' | 'clash' | 'finish' | 'won' | 'lost'—runwhile the crowd runs up the lane,clashwhile it fights an enemy crowd,finishon the stairs or against the boss, thenwonorlost.z: number— The crowd's middle:zup the lane,xacross it (0 the middle).x: number— The crowd's middle across the lane, in lane units; 0 is the middle.count: number— Members in the crowd.enemies: number[]— The enemy crowds' counts, by course order, and the one being fought.fighting: number | null— Index intoenemiesof the crowd being fought, or null when not in a clash.gates: ('left' | 'right' | null)[]— The gates taken, by course order: 'left', 'right', or null not yet.climbed: number— The finish: steps climbed (stairs) or the boss's hit points.bossHp: number— The boss's hit points left; 0 with a stairs finish.multiplier: number— The finish's multiplier (stairs), and the score: members at the finish times it.score: number— Final score, set when the run is won; 0 before then.
CrowdView
interface CrowdView
What is where.
state: CrowdRunnerState— The run's state when the view was taken; a copy.course: RunnerCourse— The course being run.members: { x: number; z: number }[]— The crowd's members, as positions (xacross,zup the lane).enemies: { index: number; at: number; x: number; count: number; members: { x: number; z: number }[] }[]— Enemy crowds still standing, with their members.obstacles: (Obstacle & { now: number })[]— Obstacles where they are now.
EnemyCrowd
interface EnemyCrowd
An enemy crowd standing in the lane: it fights the crowd one for one.
at: number— Distance up the lane from the start, in lane units.count: number— Members in the crowd; a whole number, 1 or more.x?: number— Its middle across the lane, in lane units; 0 (the default) is the middle.
GateOp
interface GateOp
What a gate does to the crowd.
op: '+' | '-' | 'x' | '/'— The arithmetic:+addsn,-subtracts it,xmultiplies by it,/divides by it and rounds down.n: number— The operand; above 0.
GatePair
interface GatePair
Two gates side by side, across the lane at at; the divider is at split (0 is the middle).
at: number— Distance up the lane from the start, in lane units.left: GateOp— The gate on the left of the divider.right: GateOp— The gate on the right of the divider.split?: number— Where the divider sits across the lane, in lane units; 0 (the default) is the middle.
Obstacle
interface Obstacle
An obstacle: anything of the crowd inside its footprint dies. x is its middle across the lane (0 the middle).
at: number— Distance up the lane from the start, in lane units.kind: 'saw' | 'wall' | 'pit'— A spinning saw (round), a wall (a block), or a pit (a hole across part of the lane).x: number— Its middle across the lane, in lane units; 0 is the middle.w: number— Its width across the lane and depth along it.d: number— Its depth along the lane, in lane units.sweep?: { range: number; rate: number }— It swings side to side: this far each way, this many times a second.
RunnerCourse
interface RunnerCourse
A course.
length: number— Lane length to the finish, in lane units.width: number— Lane width, in lane units.gates: GatePair[]— The gate pairs, by distance up the lane.obstacles: Obstacle[]— The obstacles, by distance up the lane.enemies: EnemyCrowd[]— The enemy crowds, by distance up the lane.finish: CourseFinish— How the course ends.


