Phaser AE

Crowd runner with number gates

Pro packv3.0.0-12DArcade

A crowd runner on a straight lane: choose the better gate, steer the crowd round the saws, beat the enemy crowds and climb as high as you can at the end. Courses are written by hand or made from a seed, and the pack gives you every member's position to draw.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 19, 2026

Crowd runner with number gates: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

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 from snapshot() and discards pending events. The save carries its course, so it need not be this run's. Throws crowd-runner.restore: …, changing nothing, on a save checkCrowdRunnerSave refuses.

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 by move.
  • 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' — run while the crowd runs up the lane, clash while it fights an enemy crowd, finish on the stairs or against the boss, then won or lost.
  • z: number — The crowd's middle: z up the lane, x across 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 into enemies of 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 (x across, z up 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: + adds n, - subtracts it, x multiplies 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.