The pack
scalerRunner
function scalerRunner(): Pack<{ scalerRunner: typeof api }>
Install the scaler-runner (Space Harrier) rules API.
Helpers
createRunner
function createRunner(config: RunnerConfig): ScalerRunner
Create a runner game from a configuration.
crystalCanyonKinds
function crystalCanyonKinds(): Record<string, RunnerKind>
The Crystal Canyon object table.
crystalCanyonPreset
function crystalCanyonPreset(o: CrystalCanyonOptions = {}): RunnerConfig
The Crystal Canyon configuration.
crystalCanyonStages
function crystalCanyonStages(): RunnerStage[]
The three Crystal Canyon stages.
curve
function curve(fn: (t: number) => { x: number; y: number; z: number }, duration: number, samples = 24, loop = false): DepthPathSpec
Keys sampled from a parametric curve over duration seconds.
harrierGround
function harrierGround(light: string, dark: string, dimLight: string, dimDark: string, haze: string, height = 0): DepthSurface
The Space Harrier ground: column stripes by depth bands in four colours, fading to the haze at the horizon.
keys
function keys(list: [number, number, number, number][], smooth = true): DepthPathSpec
Keys from [t, x, y, z] tuples.
mirrorPath
function mirrorPath(p: DepthPathSpec): DepthPathSpec
The mirrored copy of a path (x negated).
RUNNER_STEP
const RUNNER_STEP: number
Simulation step (seconds).
runnerDifficulty
function runnerDifficulty(d: RunnerDifficulty): RunnerTuning
Difficulty multipliers: Arcade is the original tables.
runnerPaths
function runnerPaths(depthScale = 0.62): Record<string, DepthPathSpec>
The path library shared by the Space Harrier preset and other configurations. depthScale scales every depth
in front of the camera (the arcade's formations work 10 to 60 units out; behind-the-camera depths are kept).
spaceHarrierBosses
function spaceHarrierBosses(): Record<string, RunnerBoss>
The seven bosses that come back in the stage 18 rush, by name.
spaceHarrierKinds
function spaceHarrierKinds(): Record<string, RunnerKind>
The Space Harrier object table. Land objects 5,000, sky creatures and formation targets 10,000.
spaceHarrierPreset
function spaceHarrierPreset(o: SpaceHarrierOptions = {}): RunnerConfig
The Space Harrier preset (arcade order, Normal by default).
spaceHarrierProjectiles
function spaceHarrierProjectiles(): Record<string, RunnerProjectile>
Enemy projectiles.
spaceHarrierStages
function spaceHarrierStages(): RunnerStage[]
The 18 arcade stages.
validate
function validate(c: RunnerConfig): void
Check a configuration and throw a clear error for anything unsupported.
windowOpen
function windowOpen(w: RunnerWindow | undefined, age: number): boolean
Is a periodic window open at age?
Classes
ScalerRunner
class ScalerRunner
A Super Scaler runner game (see the pack docs).
readonly config: RunnerConfig— The configuration the game was created with.readonly camera: DepthCamera— The view: its pose is set every step; draw with it.readonly field: DepthField— Every object in the world: the runner, his shots, scenery, enemies, enemy shots and boss parts.state: RunnerState— The whole game state (plain data); replaced on a restore, so read it afresh each frame.constructor(config: RunnerConfig)get stageDef(): RunnerStage— The stage being played.get playerObject(): DepthObject— The runner's field object.kind(name: string): RunnerKind | undefined— A kind's definition (undefined for the runner, shots and projectiles).get timeLeft(): number | null— Seconds shown by TIME (the longest formation or boss timer running), or null.start(stage = this.config.startStage ?? 0): void— Start a new game (fromstartStageunless given).tick(seconds: number, input: RunnerInput = {}): void— Advance bysecondsin whole 1/60 s steps (at most 10 per call).drain(): RunnerEvent[]— Notifications since the last drain.screenToTarget(sx: number, sy: number): { x: number; y: number }— The runner target (world x, feet y) under a screen point on his plane, for pointer control.snapshot(): RunnerSnapshot— Plain copy of the game.restore(s: RunnerSnapshot): void— Restore a snapshot taken from a game with the same configuration.step(input: RunnerInput = {}): void— One fixed step.addScore(points: number): void— Add points (extra lives and the TOP score follow).
Types
BossPart
type BossPart = SerpentPart | CorePart | SquadPart
A boss part.
CorePart
interface CorePart
A core (face, mothership, sorcerer) with an optional orbiting shield.
type: "core"— Always"core": what tells this part from the other kinds of boss part.kind: string— The id of the core's kind inRunnerConfig.kinds.path: string— Registered path for the core (camera frame).hits: number | null— Core hits; null for an invulnerable core (Stanley).shield?: { kind: string; count: number; radius: number; /** Radians per second (positive clockwise on screen). */ spin: number; /** The core cannot be hurt while any shield unit lives (Barbarian, Wi Wi Jumbo). */ first?: boolean; /** When the core dies, surviving units fly at the runner (the Ida boss). */ release?: boolean; }— Orbiting shield units.launches?: RunnerWave[]— Launches waves while it is present (seconds after the core appears asat).throws?: { projectile: string; every: number }— Throws ground obstacles at the runner (Wi Wi Jumbo): a projectile everyeveryseconds.
CrystalCanyonOptions
interface CrystalCanyonOptions
Options for crystalCanyonPreset.
difficulty?: RunnerDifficulty—"easy","normal"or"arcade". Default"normal".lives?: number— Lives at the start, before the difficulty's extra lives are added. Default 2.hits?: number— Hits per life (default 3).startStage?: number— First stage, 1-based. Default 1.seed?: number— Seed for the game's random stream. Default 77.
RunnerBoss
interface RunnerBoss
A boss encounter.
name: string— The boss's name, shown on screen and reported in events.parts: BossPart[]— The serpents, cores and squadrons that make up the encounter.points: number— Points for defeating it (heads and cores; squad units score their kind's points).timer?: number | null— Seconds before it leaves undefeated (TIME on screen); null: it stays until defeated.
RunnerBossState
interface RunnerBossState
A boss in progress.
name: string— The boss's name.index: number— Its place in the stage'sbosses, from 0.time: number— Seconds since the boss appeared.timer: number | null— Seconds left before it leaves undefeated; null if it stays until defeated.parts: string[]— Field ids of every live part.tracks: { t: number; x: number; y: number; z: number }[][]— Serpent head tracks (camera frame), one per serpent part.launched: number[]— Launches made so far by each core part, indexing itslauncheslist.defeated: boolean— Whether it has been defeated.left: boolean— Whether it has left undefeated.
RunnerConfig
interface RunnerConfig
A full game.
seed: number— Seed for the game's random stream (scenery, spawn jitter, volleys); the same seed and inputs replay the same game.lives: number— Lives at the start, beforetuning.livesis added.view: RunnerView— The camera and projection.player: RunnerPlayer— How the runner moves.gun: RunnerGun— The runner's cannon.kinds: Record<string, RunnerKind>— Every kind of obstacle, enemy and boss part, by id.projectiles: Record<string, RunnerProjectile>— Every enemy projectile, by id.paths: Record<string, DepthPathSpec>— Paths by id, camera frame (x right, y up, z depth). Every path also gets a mirroredid~m.stages: RunnerStage[]— The stages in play order.startStage?: number— Index of the first stage (default 0).ending: "end" | "loop"— After the last stage:end(game complete) orloopback to the first.scoring: RunnerScoring— Scoring and extra lives.timing: RunnerTiming— How long each state lasts.tuning: RunnerTuning— The difficulty multipliers (seerunnerDifficulty).
RunnerDifficulty
type RunnerDifficulty = "easy" | "normal" | "arcade"
Easy / Normal / Arcade (Arcade = the original tables).
RunnerEvent
type RunnerEvent = | { kind: "stage-start"; stage: number; number: number; name: string; bonus: boolean } | { kind: "ready" } | { kind: "boss-start"; name: string } | { kind: "boss-damage"; id: string; stage: number } | { kind: "boss-defeated"; name: string; points: number; x: number; y: number; z: number } | { kind: "boss-left"; name: string } | { kind: "shot"; id: string } | { kind: "hit"; id: string; target: string; x: number; y: number; z: number } | { kind: "deflect"; id: string; target: string; x: number; y: number; z: number } | { kind: "destroyed"; id: string; target: string; points: number; x: number; y: number; z: number; by: "shot" | "smash" | "boss" | "trip" } | { kind: "enemy-fire"; id: string; projectile: string; x: number; y: number; z: number } | { kind: "player-hit"; cause: string; fatal: boolean } | { kind: "trip"; cause: string } | { kind: "respawn" } | { kind: "extra-life"; lives: number } | { kind: "wave"; index: number; enemy: string } | { kind: "stage-clear"; stage: number; name: string } | { kind: "bonus-tally"; smashed: number; points: number } | { kind: "game-over"; score: number } | { kind: "complete"; score: number }
Notifications for presentation.
RunnerFire
interface RunnerFire
An enemy's firing schedule.
projectile: string— Projectile id.every: number— Seconds between volleys.first?: number— Seconds after spawning before the first volley (defaultevery).jitter?: number— Random extra delay up to this many seconds per volley (default 0).burst?: number— Projectiles per volley (default 1) and the delay between them (default 0.12 s).burstGap?: number— Seconds between the projectiles of a burst. Default 0.12.spread?: number— Lateral spread between projectiles of a volley, world units at the target (default 0).minDepth?: number— Only fire while at least this deep / at most this deep (defaults 12 and the view's far).maxDepth?: number— Only fire while at most this deep (world units ahead of the camera). Default the view'sfar.whenOpen?: boolean— Only fire while the vulnerability window is open.
RunnerGun
interface RunnerGun
The runner's cannon.
every: number— Seconds between shots while fire is held (continuous rapid fire).speed: number— Shot speed relative to the runner (units per second).range: number— Distance a shot travels before it fades.max: number— Most shots in flight.size: number— Shot box size (world units).
RunnerInput
interface RunnerInput
Player input for one step.
x?: number— Stick, -1 .. 1 (x right, y UP).y?: number— Stick vertical, -1 (down) .. 1 (up).target?: { x: number; y: number } | null— Absolute target for the runner (world units), from a pointer or an analog stick; overrides x/y.fire?: boolean— Fire held.
RunnerKind
interface RunnerKind
Anything in the world that is not the runner: obstacles, enemies, boss parts, bonus scenery.
w: number— Box size (world units).h: number— Box height (world units).d?: number— Box depth (world units). Default the smaller ofwandh.anchor?: "base" | "centre"—base: positioned by its bottom (things on the ground);centre(default).hp: number | null— Shots needed; null: indestructible (shots are blocked by it).points: number— Points when destroyed.lethal: boolean— Touching it costs a life.trip?: boolean— Touching it while on the ground trips the runner (bushes); flying over it is safe.deflects?: boolean— Shots bounce off it without damage even though it has hp (a dragon's body).smash?: boolean— Smashed by the bonus mount for the stage's per-object bonus.window?: RunnerWindow— Only damaged while open.fire?: RunnerFire— Fires at the runner.family?: string— Counts as a sky creature (10,000) or ground object for presentation; free text.
RunnerPlayer
interface RunnerPlayer
How the runner moves.
w: number— Box width, height and depth (world units); the anchor is his feet.h: number— Box height (world units).d: number— Box depth (world units).x: [number, number]— Lateral range [min, max] of his centre.y: [number, number]— Height range [min, max] of his feet; min is the ground whenrunis true.speedX: number— Digital-stick speeds (units per second).speedY: number— Vertical speed of the digital stick, in units per second.approach: number— Speed toward an absolute target (analog stick or pointer), units per second.run: boolean— He can run on the ground at y[0] (bushes trip him there).trip: number— Seconds a trip stalls him.hits: number— Hits per life: 1 is the arcade's one-touch death; more is an energy bar.invulnerable: number— Seconds of invulnerability after a respawn or a non-fatal hit.
RunnerPlayerState
interface RunnerPlayerState
The runner.
x: number— Centre x and feet height (world units).y: number— Height of his feet (world units).vx: number— Lateral velocity (units per second, positive right).vy: number— Vertical velocity (units per second, positive up).lean: number— -1 .. 1: leaning left/right with his lateral speed (sprite pose).grounded: boolean— On the ground (running).tripped: number— Seconds left of a trip.invulnerable: number— Seconds left of invulnerability.hits: number— Hits left in this life.alive: boolean— Whether he is alive; false from the fatal hit to the respawn.dying: number— Seconds into the death fall.
RunnerProjectile
interface RunnerProjectile
An enemy projectile.
w: number— Box width (world units).h: number— Box height (world units).speed: number— Speed (units per second, relative to the camera).homing?: number— Turn rate toward the runner in rad/s (0: straight at where he was).commit?: number— Homing stops this far in front of the runner's plane, so a late sidestep dodges it (default 8).shootable?: boolean— Can be shot down (points when it is).points?: number— Points for shooting it down; only used whenshootable.life?: number— Seconds before it fizzles (default 6).
RunnerScoring
interface RunnerScoring
Scoring and lives.
tick: number— Points added everytickEveryseconds of play (the score creeps up as the runner keeps moving).tickEvery: number— Seconds between those score ticks.extraLives: number[]— Scores that award an extra life, in order.extraEvery: number | null— After the list, another life every this many points (null: none).topScore: number— The TOP score shown before anyone beats it.
RunnerSnapshot
interface RunnerSnapshot
A snapshot of a running game.
state: RunnerState— The game state, a deep copy.field: DepthFieldSnapshot— Every object in the world, as the depth field saves it.
RunnerStage
interface RunnerStage
One stage.
name: string— The stage's name, reported in events.length: number— Distance before the boss (or the end of a bonus stage).speed: number— Scroll speed (units per second);speedsoverrides it from given distances.speeds?: { at: number; speed: number }[]— From eachatdistance into the stage the scroll speed becomesspeed(units per second), in order of distance.ground: DepthSurface— The ground pattern (and an optional ceiling).ceiling?: DepthSurface— A roof pattern over the stage, for a canyon or tunnel. Default none.look?: Record<string, string>— Presentation colours and names (sky, backdrop ...): free strings the presenter reads.scenery?: Omit<SceneryOptions, "seed" | "name"> & { seed?: number }— Streamed scenery (z measured from the stage start).waves: RunnerWave[]— Formations in order of distance.bosses?: RunnerBoss[]— Bosses in order (several: a boss rush). None: the stage ends at its length.bonus?: { perSmash: number; mount: { w: number; h: number; d: number; y: number; bob: number; bobRate: number } }— A bonus ride: invincible, lateral control only, smashsmashkinds forperSmasheach at the end.
RunnerState
interface RunnerState
The whole game state (plain data).
status: RunnerStatus— Where the game is: ready, playing, boss, clear and so on.statusTime: number— Seconds spent in the current status.stage: number— Index of the current stage inRunnerConfig.stages, from 0.stageNumber: number— Stage number shown (1-based, counting loops).loop: number— How many times the game has looped back to the first stage; 0 on the first pass.distance: number— Distance travelled in this stage.camZ: number— Camera z (absolute).speed: number— Current scroll speed (units per second).time: number— Seconds of simulated time since the game began.score: number— Points scored.topScore: number— The best score so far, starting atscoring.topScore.lives: number— Lives left.nextExtra: number— The score that earns the next extra life; Infinity when no more are due.extraIndex: number— How many entries ofscoring.extraLiveshave been passed.tickClock: number— Seconds banked towards the next creeping score tick.fireClock: number— Seconds until the cannon can fire again.player: RunnerPlayerState— The runner.timers: { id: string; left: number; members: string[] }[]— Formation timers on screen: TIME shows the largest.wavesFired: number— Waves already triggered in this stage.pending: { at: number; wave: number; member: number }[]— Pending member spawns.boss: RunnerBossState | null— The boss in progress, or null when none is.bossesDone: number— Bosses finished in this stage.stageZ: number— Camera z where this stage started (stage distances are measured from it).acc: number— Fixed-step accumulator (seconds).smashed: number— Bonus objects smashed this stage.stats: { shots: number; kills: number; deaths: number }— Totals for presentation.rng: number[]— The seeded random stream's state: four words, from the kernel'srng.snapshot(). It advances in place and is saved with the state.
RunnerStatus
type RunnerStatus = "attract" | "ready" | "playing" | "dying" | "boss" | "clear" | "tally" | "over" | "complete"
Game status.
RunnerTiming
interface RunnerTiming
State timings in seconds.
ready: number— Seconds of the READY pause before play (or a boss) begins.dying: number— Seconds of the death fall before the runner respawns (or the game ends).clear: number— Seconds the stage-clear screen shows before the next stage.tally: number— Seconds the bonus tally of a bonus stage shows before the next stage.bossIntro: number— Seconds of travel left to the boss when the stage resumes after a boss in a boss rush, so the next one arrives after this long.
RunnerTuning
interface RunnerTuning
Difficulty multipliers (see runnerDifficulty).
fireEvery: number— Enemy fire interval multiplier (bigger: less fire).projectileSpeed: number— Enemy projectile speed multiplier.speed: number— Scroll speed multiplier.hitbox: number— Runner hit box multiplier against enemies, shots and obstacles.timers: number— Boss/formation timer multiplier.lives: number— Extra lives at the start.
RunnerView
interface RunnerView
The camera and projection: how the Super Scaler view follows the runner.
width: number— Screen width and height in pixels (the arcade's 320 x 224).height: number— Screen height in pixels.focal: number— Pixels per world unit at depth 1.horizon: number— Screen y of the horizon when the runner is on the ground.pitchPerHeight: number— Horizon shift in px per unit of runner height (negative: the horizon rises as he climbs).playerDepth: number— Depth of the runner's plane in front of the camera.eyeHeight: number— Camera height when the runner is on the ground.followX: number— Share of the runner's x the camera follows (0 fixed, 1 locked behind him).followY: number— Share of the runner's height the camera follows.far: number— Farthest depth objects are drawn and enter.behind: number— Units behind the camera before objects are dropped.
RunnerWave
interface RunnerWave
A formation: count members of kind that follow path (camera frame), one every every seconds.
at: number— Stage distance at which it starts.kind: string— The id of the member's kind inRunnerConfig.kinds.count: number— How many members the formation has.every?: number— Seconds between members (default 0.25).path: string— Registered path id (seeRunnerConfig.paths);~msuffix is the mirrored path.mirror?: boolean | "alternate"— Mirror every member, or alternate plain/mirrored.offsets?: { x?: number; y?: number; z?: number }[]— Per-member origin offsets, cycled.pathEnd?: "remove" | "continue" | "hold"— What a member does at the path end (defaultremove: it flew away).timer?: number— Seconds before surviving members leave; shown as TIME while they are on screen.
RunnerWindow
interface RunnerWindow
Periodic vulnerability: open for open seconds, closed for closed, starting offset seconds into the cycle.
open: number— Seconds the window is open (vulnerable) in each cycle.closed: number— Seconds the window is closed in each cycle.offset?: number— Seconds into the cycle at age 0, to put enemies out of step. Default 0.from?: number— Closed until this age (seconds): a Tomos arrives shut. Default 0.
SerpentPart
interface SerpentPart
A dragon: a head that takes hits and a body of segments trailing its path.
type: "serpent"— Always"serpent": what tells this part from the other kinds of boss part.head: string— The id of the head's kind inRunnerConfig.kinds.body: string— The id of the body segments' kind inRunnerConfig.kinds.segments: number— Body segments.spacing: number— Seconds of the head's track between segments.path: string— Registered looping path for the head (camera frame).phase?: number— Start time on the path.hits: number— Head hits to destroy.stages?: number— Colour stages the head goes through as it is hit (presentation readsdata.stage).delay?: number— Start delay (seconds).
SpaceHarrierOptions
interface SpaceHarrierOptions
Options for spaceHarrierPreset.
difficulty?: RunnerDifficulty—"easy","normal"or"arcade"(the original tables). Default"normal".lives?: number— Lives DIP: 2, 3 (default), 4 or 5.extraLife?: number— Bonus life DIP: 5,000,000 (default) or 7,000,000.startStage?: number— First stage, 1-based (default 1).stages?: number[]— Keep only these stage numbers (1-based), in order (default all 18).seed?: number— Seed for the game's random stream. Default 1985.
SquadPart
interface SquadPart
A squadron of count units on a path, each every seconds apart in phase.
type: "squad"— Always"squad": what tells this part from the other kinds of boss part.kind: string— The id of the units' kind inRunnerConfig.kinds.count: number— How many units the squadron has.path: string— The id of the registered path the units follow (camera frame);~mafter it is the mirrored path.every: number— Seconds of path phase between units (all appear together).offsets?: { x?: number; y?: number; z?: number }[]— Per-unit origin offsets in world units, cycled through the units.mirror?: boolean | "alternate"— Mirror every unit, or alternate plain and mirrored.pathEnd?: "remove" | "hold"— At the end of a non-looping path:remove(default: flew off or passed),hold.


