The pack
platformRounds
function platformRounds(): Pack<{ platformRounds: typeof api }>
Install the renderer-independent platform rounds API.
Helpers
bumpModule
function bumpModule(): RoundsModule<BumpModuleState>
Floors bumped from below, kicks, POW, Slipice, icicles and fireballs: the Mario Bros family, as a module.
createRounds
function createRounds(def: RoundsDef): Rounds
Create a game session from a definition. Call attract() or start() before ticking.
difficultyPreset
function difficultyPreset(name: "easy" | "normal" | "arcade"): DifficultyScale
The three standard scales. Arcade reproduces the original tables; Normal is the human-playable default.
Constants
BUMP_MODULE
const BUMP_MODULE: "bump"
The id bumpModule runs under: its state is RoundsState.modules.bump.
Types
BumpDef
interface BumpDef
Floors bumped from below (Mario Bros): flips, kills and kicks.
radius?: number— Cells either side of the bumped cell that are affected. Default 1.kick?: { scores: number[]; window?: number }— Kicking flipped enemies: the chain of points; a kick withinwindowseconds of the last advances it.afterKick?: { emitter: string; kind: string; delay?: number; skipLast?: boolean }— After each kick, emit this kind from this emitter (Mario Bros coin wafers).
BumpModuleState
interface BumpModuleState
The bump module's state: POW uses, the kick chain, and what it keeps per enemy. Plain data, saved in snapshots.
pow: number— POW uses left.powFull: number— POW uses when full; 0 until the first stage with a POW block.kickChain: number— Kicks in the current chain, less one.kickTime: number— When the last kick happened, in seconds of play on the stage.drops: Record<string, number>— By enemy id: seconds after appearing that it lets go and drops (icicles), or -1 once it has.freezes: Record<string, number>— By enemy id: grounded seconds before a Slipice freezes its floor (freeze.at: 'anywhere').grounded: Record<string, number>— By enemy id: seconds a Slipice has walked on a floor.
DifficultyScale
interface DifficultyScale
Difficulty and population scaling shared by every stage (Easy / Normal / Arcade presets, or your own).
name: string— Display name.speed: number— Global enemy speed multiplier (platform-enemies speed scale). Arcade 1.interval: number— Multiplier on every emitter interval (larger: fewer enemies). Arcade 1.timer: number— Multiplier on bonus timer step intervals (larger: more time). Arcade 1.aggression: number— 0..1 multiplier the presets apply to aggressive choices (barrel steering, wild barrels, chasers). Arcade 1.population: number— Multiplier on enemy population caps (emittermax, roster sizes where a preset honours it). Arcade 1.grace: number— Multiplier on how long stunned enemies stay flipped and on hammer time (larger: kinder). Arcade 1.
FeetRef
type FeetRef = { x: number; feet: number } | { marker: string; index?: number; dx?: number }
Where a body stands: feet position or a marker's floor.
InterludeDef
interface InterludeDef
An intermission played before or after a stage (presentation reads kind and phaseTime).
kind: string— Free name read by presenters: 'how-high', 'carry', 'fall', 'phase' ...seconds: number— Seconds.everyAttempt?: boolean— Play it before every attempt, not only the first (Donkey Kong's "How high can you get?"). Default false.
JumpOverDef
interface JumpOverDef
Jump-over scoring: points for clearing enemies in one jump (Donkey Kong: 100, 300, 800).
scores: number[]— Points by count: index 0 for one enemy, the last entry for that many or more.below?: number— How far below the feet an enemy may pass and still count, world units. Default 24.falls?: boolean— Also score when the player merely falls over enemies (not only in a jump). Default false.shown?: number[]— Values to display instead ofscores(Donkey Kong shows 800 for three barrels but pays 500). Defaultscores.
KindRules
interface KindRules
What happens when the player touches an enemy kind, bumps it, smashes it or jumps over it.
contact?: "kill" | "harmless" | "collect"— Touching it while active: the player dies ('kill', default), nothing ('harmless') or it is collected ('collect').stunnedContact?: "kick" | "kill" | "harmless"— Touching it while flipped: kick it away ('kick', default for kinds with stun rules), 'kill' or 'harmless'.collect?: number— Points for collecting it (contact: 'collect').smash?: number | number[]— Hammer smash points; an array picks one at random per smash. Omit: a hammer cannot destroy it.jump?: boolean— Counts toward jump-over scoring when the player jumps over it.bump?: "flip" | "kill" | "none"— A floor bumped from below under it: 'flip' (platform-enemies hit rules), 'kill' (destroyed), 'none'. Default 'flip'.bumpScore?: number— Points for a bump: per flip ('flip') or per kill ('kill'). Default 0.pow?: "flip" | "kill" | "none"— The POW block destroys it while it touches a floor (or is airborne, withpowAir). Default: flips it like a bump.powAir?: boolean— POW also reaches it in the air. Default false.powScore?: number— Points when a POW destroys it. DefaultbumpScore.counts?: boolean— Counts toward a 'clear' objective. Default true.dropAfter?: number | [number, number]— Seconds after spawning before a hanging body (flight profile) starts to fall (icicles).shatterOnLand?: boolean— Removed when it lands after falling ('shatter'). Default false.expire?: number— Removed this many seconds after spawning (fireballs that burn out).freeze?: { at?: "centre" | "anywhere"; after?: number | [number, number]; material?: string; tag?: string }— Freezes the floor run it walks on intomaterial, then is removed (Slipice): when it reaches the run's centre (at: 'centre', default) or afteraftergrounded seconds (at: 'anywhere'). Withtag, only runs whose cells carry that tag freeze. A run already of that material is passed over.removeOutside?: boolean— Removed when it leaves the map through an open side. Default true.edgeExit?: boolean— Removed when it reaches the side of the map it is heading for (fireballs crossing a wrapping screen). Default false.young?: { seconds: number; contact: "destroy" | "harmless" }— While younger thanseconds, touching it 'destroy's it (no points) or is 'harmless' (forming icicles).
ObjectiveDef
type ObjectiveDef = /** Reach a zone (Donkey Kong: Pauline's girder). */ | { type: "reach"; zone: ZoneRef; grounded?: boolean } /** Defeat `count` enemies of `kinds` (default: every counted kind), none left alive (Mario Bros phases). */ | { type: "clear"; kinds?: string[]; count?: number } /** Remove every map cell tagged `tag` by walking or jumping over it (Donkey Kong rivets). */ | { type: "cells"; tag: string; score?: number; replace?: CellSpec; over?: number } /** Take every item pickup (or those named `names`): gems, stars, keys. */ | { type: "pickups"; names?: string[] } /** Collect `count` enemies of `kinds` (contact 'collect' or bump 'kill'); `perfect` points when all are taken. */ | { type: "collect"; kinds: string[]; count?: number; perfect?: number | number[] }
A stage objective. Stages end when ALL their objectives are met.
PickupDef
type PickupDef = /** A hammer (Donkey Kong): smash enemies whose rules allow it; no jumping or climbing while held. */ | { type: "hammer"; id?: string; at: PointRef; w?: number; h?: number; duration: number; warn?: number; reach?: number; above?: number; profile?: WalkerProfileInput; pause?: number } /** A bonus item worth points (Pauline's umbrella, hat and purse). */ | { type: "item"; id?: string; name?: string; at: PointRef; w?: number; h?: number; score: number }
A pickup.
PickupState
interface PickupState
A pickup's state.
id: string— Id.type: "hammer" | "item"— 'hammer' or 'item'.name: string— Item name (render).x: number— Centre x.y: number— Centre y.w: number— Width.h: number— Height.taken: boolean— Taken this attempt.
PointRef
type PointRef = { x: number; y: number } | { marker: string; index?: number; dx?: number; dy?: number }
A point given directly (x centre, y centre) or a map marker's cell centre (plus an offset).
PowDef
interface PowDef
A POW block: map cells tagged tag; bumping one from below hits every grounded enemy.
tag?: string— Cell tag. Default 'pow'.uses?: number— Uses when full. Default 3.refill?: boolean— Refill to full when this stage starts. Default false (uses carry over between stages).score?: number— Points per POW. Default 0.
Rect
interface Rect
A world rectangle by its top-left corner and size.
x: number— Left, world units.y: number— Top, world units (y down).w: number— Width.h: number— Height.
RespawnDef
interface RespawnDef
Where and how the player comes back after a death that does not restart the stage (Mario Bros).
at: FeetRef— Respawn point (feet).platform?: { width: number; seconds: number; kind?: string }— A temporary platform under the respawn point: width and seconds before it vanishes (or the player walks off).
Rounds
interface Rounds
A running game.
readonly def: RoundsDef— The definition.readonly state: RoundsState— A copy of the presenter-facing state.readonly stage: StageDef— The current stage's resolved definition.readonly map: PlatformMap— The current level model.readonly movers: Movers— The current movers.readonly world: WalkerWorld— The current walker world.readonly enemies: EnemyDirector— The current enemy director.readonly player: string— The player body id ('player').readonly difficulty: DifficultyScale— The difficulty in force: the definition's, or the lastsetDifficulty. A copy.levelStages(level: number): string[]— Stage ids of a level.start(o?: { level?: number; index?: number; skipIntro?: boolean }): void— Start a new game (optionally at a level and stage index).attract(o?: { level?: number; index?: number }): void— Show a stage without playing (attract mode: the player is hidden, enemies run).tick(seconds: number, input: RoundsInput): number— Run whole fixed steps for elapsed seconds; returns the number run.step(input: RoundsInput): void— One fixed step.setDifficulty(d: DifficultyScale): void— Change the difficulty (applies to stages built from now on, and enemy speed at once).setPlayerProfile(profile: WalkerProfile | WalkerProfileInput): void— Replace the player's movement profile now and for the rest of the game (respawns and hammers restore to it).drain(): RoundsEvent[]— Events since the last drain.snapshot(): RoundsSnapshot— Serialisable state.restore(s: RoundsSnapshot): void— Restore a snapshot taken from a session with the same definition.
RoundsDef
interface RoundsDef
The whole game: stages, their order, the player, lives and timing.
stages: Record<string, StageSource>— Stages by id.sequence: SequenceDef— Order.player: { profile: WalkerProfile | WalkerProfileInput; tags?: string[] }— The player's body: its walker profile (a full profile or the fields that differ from the default) and tags.lives?: number— Lives at the start (including the one in play). Default 3.maxLives?: number— Most lives held. Default 9.extraLives?: number[]— Scores that award an extra life (each once). Default none.extraEvery?: number— After the last threshold, another life every this many points (0: none). Default 0.timings?: { intro?: number; ready?: number; death?: number; clear?: number; over?: number }— Phase durations, seconds.difficulty?: DifficultyScale— Difficulty scale. Default Arcade.seed?: number— Seed. Default 1.step?: number— Fixed step, seconds. Default 1/60.hiScore?: number— Initial high score. Default 0.maxSteps?: number— Cap on a tick's steps. Default 8.modules?: RoundsModule[]— Rules modules, called in this order at fixed points of each step (seeRoundsModule). Default[bumpModule()]: floors bumped from below, kicks, POW, Slipice, icicles and fireballs. Add a game's own after it, or leave it out where a game has none of those (its kind and stage fields then do nothing).
RoundsEvent
type RoundsEvent = | { kind: "game-start" } | { kind: "stage-start"; level: number; index: number; stage: string; attempt: number } | { kind: "phase"; phase: RoundsPhase } | { kind: "score"; points: number; reason: string; x: number; y: number; total: number } | { kind: "extra-life"; lives: number } | { kind: "death"; cause: string; x: number; y: number } | { kind: "life-lost"; lives: number } | { kind: "jump-over"; count: number; points: number; shown: number; x: number; y: number } | { kind: "spawned"; enemy: string; enemyKind: string; emitter: string; x: number; y: number } | { kind: "pickup"; id: string; type: "hammer" | "item"; name: string; x: number; y: number } | { kind: "hammer-end"; id: string } | { kind: "smash"; enemy: string; enemyKind: string; points: number; x: number; y: number } | { kind: "cell"; col: number; row: number; tag: string; points: number } | { kind: "bump"; col: number; row: number; x: number; y: number } | { kind: "flip"; enemy: string; enemyKind: string; result: "flipped" | "angered" | "unflipped" } | { kind: "kick"; enemy: string; enemyKind: string; points: number; chain: number; x: number; y: number } | { kind: "collect"; enemy: string; enemyKind: string; points: number; x: number; y: number } | { kind: "destroy"; enemy: string; enemyKind: string; cause: string; points: number; x: number; y: number } | { kind: "pow"; uses: number; col: number; row: number } | { kind: "sink"; enemy: string; enemyKind: string; spawned: string; x: number; y: number } | { kind: "freeze"; enemy: string; row: number; cols: number[] } | { kind: "toggle"; id: string; on: boolean } | { kind: "timer"; value: number } | { kind: "timer-zero" } | { kind: "stage-clear"; stage: string; bonus: number; result: string } | { kind: "level-clear"; level: number } | { kind: "game-over"; score: number }
Events, in order, from drain().
RoundsInput
interface RoundsInput
Player input for one step.
x: number— -1 left .. 1 right.y: number— -1 up .. 1 down.jump: boolean— Jump held.
RoundsModule
interface RoundsModule<S = unknown>
A rules module: game rules the session calls at fixed points of each step, with a state of its own (plain data,
carried by snapshots, shown in RoundsState.modules). bumpModule() is one; a game passes its own in
RoundsDef.modules. Every hook is optional.
id: string— Unique among the modules: names its state inRoundsState.modulesand in snapshots.init?(): S— Its state for a new game: plain data. Default{}.build?(api: RoundsModuleApi, state: S): void— A stage has been built: at its start, and again when a death restarts it.beforeMove?(api: RoundsModuleApi, state: S): void— Each step of play, before the bodies move (after enemy ages have advanced).bump?(api: RoundsModuleApi, state: S, at: { col: number; row: number; x: number; y: number }): void— The player bumped a map cell from below;x,yis where. The session has already reported abumpevent.contact?(api: RoundsModuleApi, state: S, e: Readonly<EnemyState>, b: Readonly<WalkerBody>): boolean | void— The player touches an enemy whose body is enabled. Return true when the module has dealt with it: the session's own contact rule then leaves it alone.enemyEvent?(api: RoundsModuleApi, state: S, ev: WalkerEvent, e: Readonly<EnemyState>): void— A world event about an enemy's body (landed,out-of-bounds...), after the session's own handling (which removes an enemy that leaves the map).enemies?(api: RoundsModuleApi, state: S): void— Each step of play, and of attract mode, after contacts and sinks: rules about the enemies alone.removed?(api: RoundsModuleApi, state: S, id: string): void— An enemy has been removed, for any reason: forget what the module kept about it.
RoundsModuleApi
interface RoundsModuleApi
What a rules module reaches while the session runs. Bodies and enemies come live and read-only: read them within the call, and change things through these calls and the parts' own APIs.
readonly stage: StageDef— The current stage's resolved definition.readonly map: PlatformMap— The level.readonly world: WalkerWorld— The walker world.readonly enemies: EnemyDirector— The enemy director.readonly player: string— The player's body id.readonly time: number— Seconds of play on this stage attempt.readonly dt: number— The fixed step, seconds.rules(kind: string): KindRules— A kind's rules on this stage; empty when it has none.stuns(kind: string): boolean— Whether a kind can be flipped: it has stun rules.body(e: Readonly<EnemyState>): Readonly<WalkerBody> | null— An enemy's body, live and read-only, or null when it has none.age(id: string): number— Seconds since an enemy appeared; 0 for one the session does not know.random(): number— The session's seeded stream: a number in [0, 1).award(points: number, reason: string, x?: number, y?: number): void— Add points, with where to show them.emit(e: RoundsEvent): void— Report an event.remove(e: Readonly<EnemyState>, reason: string): void— Remove an enemy. It counts toward a 'clear' objective unlessreasonis 'sink', 'gone', 'expired' or 'frozen', or its kind hascounts: false.destroy(e: Readonly<EnemyState>, b: Readonly<WalkerBody>, cause: string, points: number): void— Destroy an enemy for points (causesays why), or collect it when its kind's contact is 'collect'.die(cause: string): void— Kill the player.spawnLater(o: { kind: string; emitter?: string; at?: { x: number; feet: number }; facing?: -1 | 1; delay?: number }): void— Spawn one body ofkindlater: fromemitter, or atat(feet) facingfacing,delayseconds from now (default 0).lastDefeated(): boolean— Whether a 'clear' objective has all it needs and nothing it counts is left: the last enemy is down.
RoundsPendingSpawn
interface RoundsPendingSpawn
A spawn still to come: from the stage's spawns[src], or a one-off (src -1: kicks, sinks, spawnLater).
src: number— Index into the stage'sspawns, or -1 for a one-off.kind: string— Enemy kind.emitter?: string— Emitter to spawn from, if any.at?: { x: number; feet: number }— Where (feet), if not from an emitter or the spawn's points.facing?: -1 | 1— Facing.due: number— When it is due, seconds of play.left: number— Spawns left: -1 unlimited.cyc: number— Which of the spawn's points comes next (place: 'cycle').
RoundsPhase
type RoundsPhase = "attract" | "intro" | "before" | "ready" | "play" | "dying" | "clear" | "after" | "over"
Session phase.
RoundsSession
interface RoundsSession
The session's own state, as a snapshot holds it: plain data. Read it only to store it; restore puts it back.
phase: RoundsPhase— The phase.phaseTime: number— Seconds in the phase.interlude: string— The interlude playing in a 'before' or 'after' phase, or ''.level: number— Level number, from 1.index: number— Stage index within the level, from 0.loop: number— Sequence loops completed.attempt: number— Attempt on this stage, from 1.score: number— Score.hi: number— High score.lives: number— Lives, the one in play included.extras: number— Extra lives awarded so far.time: number— Seconds of play on this stage attempt.timer: number— The bonus timer's value.timerStart: number— The bonus timer's value at the stage's start.timerAcc: number— Seconds towards the timer's next tick.timerOn: boolean— Whether the timer is running.hammer: string— The id of the hammer held, or ''.hammerLeft: number— Seconds the hammer has left.pickups: PickupState[]— The stage's pickups and whether each has been taken.removedCells: [number, number][]— Cells a 'cells' objective has removed, [column, row], kept across deaths.keptPickups: string[]— Item pickups taken, kept across restarts withkeepPickups.touchedCells: [number, number][]— 'cells' objective cells the player is over now, [column, row]: removed once left behind.defeated: number— Enemies defeated toward a 'clear' objective.collected: number— Enemies collected toward a 'collect' objective.pending: RoundsPendingSpawn[]— Spawns still to come.pause: number— Seconds the action stays frozen (a hammer smash).zeroAt: number— When the timer reached zero, seconds of play, or -1: itsonZerowaitszeroDelayfrom here.jumpSet: string[]— Enemies cleared in the jump under way, for jump-over scoring.ages: Record<string, number>— By enemy id: seconds since it appeared.modules: Record<string, unknown>— By module id: each rules module's state.toggles: { id: string; on: boolean; t: number }[]— Toggles: on or off, and seconds into the cycle.respawnLeft: number— Seconds the respawn platform has left.respawnMoved: boolean— Whether the player has moved since respawning (the platform then goes when left).deathCause: string— Why the player died last, or ''.result: string— How the stage ended ('objective', 'time'), or '' while it runs.ramp: number— The enemy speed ramp in force (the stage'sramp), 1 without one.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.events: RoundsEvent[]— Events not yet drained.
RoundsSnapshot
interface RoundsSnapshot
Serialisable session state (sub-packs snapshot themselves inside).
s: RoundsSession— Session fields.map: PlatformMapSnapshot— Map.movers: MoversSnapshot— Movers.world: WalkerWorldSnapshot— World.enemies: EnemiesSnapshot— Enemies.difficulty: DifficultyScale— The difficulty in force, whichsetDifficultymay have changed since the definition.player: WalkerProfile | null— The player profilesetPlayerProfileset, or null for the definition's.
RoundsState
interface RoundsState
Everything a presenter reads.
phase: RoundsPhase— Phase.phaseTime: number— Seconds in the phase.interlude: string— Interlude being played ('before'/'after' phases) or ''.level: number— Level number.stageIndex: number— Stage index within the level.stage: string— Stage id.stageName: string— Stage display name.theme: string— Stage theme.loop: number— Sequence loops completed.attempt: number— Attempt on this stage.score: number— Score.hiScore: number— High score.lives: number— Lives including the one in play.extras: number— Extra lives awarded so far.timer: { value: number; start: number; warning: boolean; active: boolean }— Bonus timer value (0 without a timer), its start and whether it is in its warning band.hammer: { active: boolean; left: number; warning: boolean; id: string }— Hammer held: seconds left, and the warning flag.pow: { uses: number; full: number }— POW uses left and full, frombumpModule(0 and 0 without it).modules: Record<string, unknown>— Copies of each rules module's state, by module id.pickups: PickupState[]— Pickups.cellsLeft: number— Cell objective: cells left.defeated: number— Clear objective: defeated so far and needed.needed: number— Enemies needed for a 'clear' objective (0 without one).collected: number— Collect objective: collected so far and needed.toCollect: number— Items needed for a 'collect' objective.toggles: { id: string; on: boolean; progress: number }[]— Toggles: on, seconds into the current state and render progress 0..1 (1 = fully on).respawning: boolean— Respawn platform present (Mario Bros).deathCause: string— Why the player died last ('' none).result: string— How the last stage ended ('' while running): 'objective', 'time'.time: number— Seconds of play on this stage attempt.speed: number— Current enemy speed multiplier (difficulty x ramp).difficulty: string— Difficulty name.
SequenceDef
interface SequenceDef
The order of stages: one list per level; after the last level the list at loopFrom onward repeats.
levels: string[][]— levels[i] lists the stage ids of level i + 1.loopFrom?: number— Level index (0-based) to repeat from after the last. Default the last level (it repeats forever).maxLevel?: number— Highest level (the game ends after it). Default unlimited.
SinkDef
interface SinkDef
A zone that swallows enemies of some kinds and may turn them into another (an oil drum lighting fireballs).
zone: ZoneRef— Zone.kinds: string[]— Kinds swallowed.spawn?: { kind: string; max?: number; facing?: -1 | 1; delay?: number; at?: FeetRef }— Spawn this kind in their place (atat, default the zone's centre bottom), up tomaxalive, afterdelayseconds.score?: number— Points awarded when swallowed. Default 0.
SpawnDef
interface SpawnDef
A body spawned when a stage starts, or later, once or repeatedly.
kind: string— Enemy kind.at?: FeetRef— Where (feet).points?: FeetRef[] | ((rt: StageRuntime) => FeetRef[])— Candidate points (feet), chosen byplace; or computed when due.place?: "cycle" | "random" | "opposite-player" | "opposite-edge"— How to choose amongpoints: 'cycle' (default), 'random', or 'opposite-player' (random among points on the half of the map away from the player); 'opposite-edge' ignores points and appears at the map side away from the player at the player's feet height, facing them (Mario Bros green fireball).emitter?: string— Emit from this emitter instead (its points and timing rules apply to the body).facing?: -1 | 1— Facing. Default 1 (toward the player for 'opposite-edge').delay?: number— Seconds after play starts. Default 0.every?: number | [number, number] | ((rt: StageRuntime) => number)— Repeat every this many seconds (a random interval in a range, or computed now) after the first. Default: once.max?: number | ((rt: StageRuntime) => number)— Most alive of this kind for a spawn to happen (a skipped spawn waits for the next interval). Default unlimited.count?: number— Most spawns in total from this entry. Default unlimited when repeating, else 1.when?(rt: StageRuntime): boolean— A condition checked at each due time (a false result skips that spawn).
StageContext
interface StageContext
Context passed to stage builders: build a stage for a level, attempt and difficulty.
level: number— Level number, from 1.index: number— Stage index within the level, from 0.stage: string— Stage id.loop: number— Times the sequence has looped (0 on the first pass).difficulty: DifficultyScale— Difficulty scale.attempt: number— Attempt number on this stage (1 on the first).
StageDef
interface StageDef
One stage: a level plus its rules.
name?: string— Display name ('25m', 'PHASE 1').theme?: string— Free label for presenters ('barrels', 'conveyors', 'bonus' ...).map: PlatformMapDef— The level (platform-map definition).movers?: MoverDef[]— Moving surfaces (platform-movers definitions).kinds?: Record<string, EnemyKind>— Enemy kinds (platform-enemies).behaviours?: Record<string, EnemyBehaviour>— Custom enemy behaviours (platform-enemiesbehaviours).emitters?: EmitterDef[]— Emitters (intervals are scaled by the difficulty).exits?: ExitDef[]— Exits (pipes).spawns?: SpawnDef[]— Initial and scheduled spawns.start?: FeetRef— Player start (feet). Default the 'player' marker.facing?: -1 | 1— Player facing at the start. Default 1.profile?: WalkerProfileInput— Player profile override for this stage.objective: ObjectiveDef | ObjectiveDef[]— Objectives (all must be met).timer?: TimerDef— Bonus timer.rules?: Record<string, KindRules>— Per-kind interaction rules.pickups?: PickupDef[]— Pickups.jumpOver?: JumpOverDef— Jump-over scoring.bump?: BumpDef— Floors bumped from below.pow?: PowDef— POW block.sinks?: SinkDef[]— Sinks.toggles?: ToggleDef[]— Timed cell toggles.ramp?: { every: number; step: number; max: number }— Enemy speed ramp within the stage: +stepeveryeveryseconds, up tomax(multiplier).onDeath?: "restart" | "respawn"— After a death: rebuild the stage ('restart', Donkey Kong) or continue it with a respawn ('respawn', Mario Bros). Default 'restart'.respawn?: RespawnDef— Respawn rules foronDeath: 'respawn'.keepCells?: boolean— Keep cells removed by a 'cells' objective across deaths. Default true.before?: InterludeDef— Interlude before the stage starts.after?: InterludeDef— Interlude after the stage is cleared.deaths?: { falls?: boolean; hazards?: boolean; outside?: boolean; crush?: boolean }— Deaths from fatal landings, hazards, leaving through the bottom, and crushes (walker embeds caused by a moving surface or a map edit; plain overlaps never kill). Default all true.keepPickups?: boolean— Item pickups taken stay taken after a death that restarts the stage (collected gems stay collected). Default false.clearBonus?: number— Points added when the stage's objectives are met (a level-clear bonus). Default 0.speed?: number— Enemy speed multiplier for this stage, on top of the difficulty (a per-level speed-up). Default 1.inset?: number— Contact inset (world units) for player/enemy touches. Default 2.hazards?: ZoneRef[]— Zones that kill the player on contact (a giant ape, a burning oil drum, a fatal bottom girder).step?(rt: StageRuntime, dt: number): void— A policy hook called after the built-in rules every fixed step of play.
StageRuntime
interface StageRuntime
What the presenter and hooks can reach while a stage runs.
readonly map: PlatformMap— The level.readonly movers: Movers— Movers (always present, possibly empty).readonly world: WalkerWorld— Walker world.readonly enemies: EnemyDirector— Enemy director.readonly player: string— Player body id.readonly time: number— Seconds since play started on this stage attempt.award(points: number, reason: string, x?: number, y?: number): void— Add points (with a popup position).kill(cause: string): void— Kill the player now.clear(reason?: string): void— End the stage as cleared now.random(): number— Seeded random number in [0, 1).
StageSource
type StageSource = StageDef | ((ctx: StageContext) => StageDef)
A stage given as data, or built per level and difficulty.
TimerDef
interface TimerDef
A countdown that pays out at the end of a stage, kills at zero, or ends the stage at zero.
start: number— Starting value (Donkey Kong: 5000 at level 1).step: number— Amount removed per tick.every: number— Seconds per tick (scaled by the difficulty'stimer).onZero?: "die" | "end" | "none"— At zero: the player dies ('die', Donkey Kong), the stage ends ('end', bonus phases) or nothing ('none'). Default 'die'.award?: boolean— Add the remaining value to the score when the stage is cleared. Default true.warn?: number— Value at or below whichwarningis reported. Default 1000.emitter?: string— Tick once per spawn from this emitter instead of everyeveryseconds (Donkey Kong 25 m: one tick per barrel).zeroDelay?: number— Seconds between reaching zero andonZerotaking effect (Donkey Kong: about 256 frames). Default 0.stopEmitters?: string[]— Emitters switched off when the timer reaches zero (Kong stops throwing).
ToggleDef
interface ToggleDef
Map cells that switch between two specs on a timetable (Donkey Kong 50 m retracting ladders).
id: string— Id (render state, events).cells: { col: number; row: number }[]— Cells switched.on: CellSpec— Spec while on (extended).off: CellSpec— Spec while off (retracted).period: [number, number]— Seconds on, then seconds off.phase?: number— Seconds into the cycle at the start. Default 0.travel?: number— Seconds the change takes (render progress only; cells switch at the start of the change). Default 0.5.hold?: boolean— Do not switch off while the player is on one of the cells (the switch waits). Default false.
ZoneRef
type ZoneRef = Rect | { marker: string; index?: number; w?: number; h?: number; dx?: number; dy?: number }
A rectangle given directly, or centred horizontally on a map marker (the index-th of that name, default the first) with its bottom on the marker's floor line.


