The pack
platformWalker
function platformWalker(): Pack<{ platformWalker: typeof api }>
Install the renderer-independent platform walker API.
Helpers
bubbleBobbleProfile
function bubbleBobbleProfile(o: PresetOptions = {}): WalkerProfile
Bubble Bobble (1986): one-way platforms jumped through from below, variable jump height, air steering,
falling out of the bottom re-enters at the top (map wrapY). Speeds and heights are estimates from play.
createWalkerWorld
function createWalkerWorld(o: WalkerWorldOptions): WalkerWorld
Create a walker world over a map.
jumpmanProfile
function jumpmanProfile(o: PresetOptions = {}): WalkerProfile
Donkey Kong's Jumpman (1981). Walk 2 px in 3 frames (40 px/s), climb 2 px per 5 frames (24 px/s), the air moves 1 px in 2 frames (30 px/s) (TASVideos frame analyses). The jump is a fixed arc chosen at take-off with no steering and no jumping from ladders. The arc height (about 14 px, enough to clear a barrel) and the fall limit (falls taller than Mario are fatal) are estimates from play; no disassembly figure is published.
labPlayerProfile
function labPlayerProfile(o: PresetOptions = {}): WalkerProfile
A friendly modern-arcade player for custom levels: variable jump, air control, ladders and bars.
lodeRunnerProfile
function lodeRunnerProfile(o: PresetOptions = {}): WalkerProfile
Lode Runner (1983): grid-aligned running and climbing at one speed, no jump, constant-speed falls, catching ladders and bars while falling, stepping off ladders sideways at any row, dropping from bars. Speeds are estimates (the runner crosses a 28-column screen in about five seconds).
marioBrosProfile
function marioBrosProfile(o: PresetOptions = {}): WalkerProfile
Mario Bros (1983): momentum walking with a skid, a high fixed arc with slight steering, head bumps on the solid floors above, the screen wrapping left and right, ice floors. Speeds and heights are estimates from play.
snowBrosProfile
function snowBrosProfile(o: PresetOptions = {}): WalkerProfile
Snow Bros (1990): like Bubble Bobble with down plus jump dropping through floors. Estimates from play.
spacePanicProfile
function spacePanicProfile(o: PresetOptions = {}): WalkerProfile
Space Panic (1980): walk floors and climb ladders, no jump; aliens fall into dug holes (a dig-trap rule). Estimates.
walkerProfile
function walkerProfile(input: WalkerProfileInput = {}, base?: WalkerProfile): WalkerProfile
Build a complete profile: defaults (a 12 x 16 body for 8-unit tiles), then a base, then overrides.
Types
AirHorizontal
type AirHorizontal = "fixed" | "keep" | "control"
How horizontal speed behaves in the air during a jump.
ClimbProfile
interface ClimbProfile
Ladders.
enabled: boolean— May climb at all.speed: number— Climbing speed upward, units per second.downSpeed: number— Climbing speed downward, units per second.snap: number— How far from a ladder's centre a body may be and still grab it, world units.glide: number— Centring speed toward the ladder's centre while climbing, units/s. 0 snaps at once.exit: "ends" | "anywhere"— Leave ladders only at their ends ('ends', Donkey Kong) or sideways at any row onto floor or bar ('anywhere', Lode Runner).broken: "block" | "pass" | "up"— Broken ladder cells stop climbers ('block', Donkey Kong's Jumpman), are passed ('pass', barrels), or are passed going up only ('up': pull yourself up a broken ladder, but never climb down one).grabInAir?: boolean— Pressing up or down while airborne (jumping or falling) grabs a ladder withinsnap(a house rule for Donkey Kong; the arcade never does). Default false.catchWhenFalling: boolean— A falling body that drops into a ladder cell catches it (Lode Runner).enterFromTop: boolean— Pressing down on a ladder's top enters it.sideTolerance: number— How close to a row boundary feet must be to step off sideways ('anywhere'), world units.exitToAir?: boolean— Withexit: 'anywhere': a sideways step off a ladder is allowed even where nothing supports the body beside it, so it walks off into the air and falls (Lode Runner). Absent or false: only onto floor, bar or another ladder.alignToRow?: boolean— Withexit: 'anywhere': a sideways push while between rows first moves the climber to the nearest row line (within a tile, on the ladder) from which that sideways step is possible, so holding a direction ends in a step off (Lode Runner centres vertically; a floors-only exit such as Space Panic's reaches the floor). Absent or false: no effect.
FallProfile
interface FallProfile
Falling.
gravity: number— Gravity, units/s².maxSpeed: number— Terminal falling speed, units per second.mode: "accelerate" | "constant"— 'accelerate' under gravity, or 'constant' speed (maxSpeed) when falling without a jump (Lode Runner).walkOffCarry: number— Fraction of horizontal ground speed kept when walking off an edge (0: drop straight down).airAccel: number— Steering acceleration while falling (not jumping), units/s²; 0 for none.death: number | null— Longest safe fall from the highest point reached, world units; longer falls are reported fatal.null: never.bounce: number— On landing faster thanbounceMin, bounce up with this fraction of the landing speed (barrels). 0: none.bounceMin: number— Minimum landing speed that bounces, units per second.centre?: number— Grid games: speed (units per second) at which a body falling without a jump glides to the centre of its column, so it drops cleanly into a one-cell hole (Lode Runner centres fallers). 0 or absent: off (the default).from?: "apex" | "takeoff"— Where fall heights are measured from: the highest point reached ('apex', default) or, during a jump, the height it started from ('takeoff', Donkey Kong: death 15 px below take-off).
FlightProfile
interface FlightProfile
Free flight (no gravity): flying enemies, debug cameras.
enabled: boolean— Fly instead of walking.speed: number— Speed on each axis, units per second.platforms: boolean— Collide with one-way platforms from above.
HangProfile
interface HangProfile
Hand-over-hand bars.
enabled: boolean— May hang at all.speed: number— Speed along a bar, units per second.catchWhenFalling: boolean— A falling body catches a bar it falls into (except the bar it dropped from).
JumpKind
type JumpKind = "none" | "fixed" | "variable"
How a jump is shaped.
JumpProfile
interface JumpProfile
Jumping.
kind: JumpKind— 'none' (Lode Runner, Space Panic), 'fixed' (arc decided at take-off: Donkey Kong, Mario Bros), 'variable' (release cuts it).speed: number— Upward take-off speed, units per second.gravity: number | null— Gravity while the jump rises and falls, units/s².nullusesfall.gravity.horizontal: AirHorizontal— 'fixed': horizontal speed set at take-off from the stick (airSpeed); 'keep': ground speed carries; 'control': steer withairAccel.airSpeed: number— Horizontal speed in the air for 'fixed' and the steering target for 'control', units per second.airAccel: number— Steering acceleration for 'control', units/s².cut: number— Variable jumps: upward speed is multiplied by this when jump is released while rising.fromLadder: boolean— May jump while climbing.fromHang: boolean— May jump while hanging from a bar.coyote: number— Seconds after walking off an edge during which a jump is still allowed.buffer: number— Seconds a jump press is remembered before landing.wallBounce: boolean— Bounce back off walls hit in the air (horizontal speed reverses) instead of stopping.steer?: number— For 'fixed' and 'keep' jumps: steering acceleration toward the stick's direction atairSpeed, units/s² (a little air control). Default 0 (none).
MaterialRule
interface MaterialRule
Per-material ground behaviour.
traction: number— Braking multiplier (ice ~0.08): how quickly speed bleeds away when the stick is released or reversed.accel?: number— Acceleration multiplier. Default:traction.speed?: number— Top speed multiplier. Default 1.
PresetOptions
interface PresetOptions
Options shared by the presets: the tile size the level uses (presets are authored for 8-unit tiles at 60 Hz).
tile?: number— Tile size of the level in world units; every distance and speed scales by tile / 8. Default 8.
WalkerBody
interface WalkerBody
A body in the world. Read through world.body(id) or world.bodies(); change through world calls.
id: string— Unique id.tags: string[]— Free tags for rules and controllers ('player', 'enemy', 'barrel').x: number— Centre x, world units.y: number— Centre y, world units.feet: number— Bottom edge (feet), world units.vx: number— Velocity x, units per second.vy: number— Velocity y, units per second.facing: -1 | 1— -1 left, 1 right.mode: WalkerMode— Movement mode.support: WalkerSupport | null— What it stands on (ground mode), else null.ladderCol: number | null— The ladder column being climbed, or null.jumping: boolean— Inside a jump (as opposed to falling after walking off).apex: number— Highest point (smallest feet y) since leaving the ground: fall heights are measured from it.stun: number— Seconds of stun left (0 when not stunned). While stunned, intents are ignored.flipped: boolean— Stunned upside-down (flipped) rather than dazed.ghost: boolean— Knocked: no intents, ballistic;ghostpasses through everything.speedScale: number— Multiplier on walk, climb, hang and flight speeds (anger, hurry-up).enabled: boolean— Disabled bodies are not moved, sensed or carried.outside: boolean— Below an open map bottom (or beyond an open side).embedded: boolean— Overlapping a solid cell (after a map edit or a crush).wrapped: boolean— Wrapped across the map this step: renderers should snap instead of interpolating.snapped: boolean— Moved discontinuously this step (wrap, teleport): renderers should snap.intent: WalkerIntent— The current intent.profile: WalkerProfile— Movement rules.sensors: WalkerSensors— Sensor readings from the end of the last step.modeTime: number— Seconds in the current mode.
WalkerBodyDef
interface WalkerBodyDef
How to add a body. Position by centre (x, y) or by feet (x, feet).
id?: string— Unique id; defaults to${name}-${n}.name?: string— Id prefix whenidis omitted. Default 'body'.x: number— Centre x.y?: number— Centre y (or givefeet).feet?: number— Feet y (bottom edge).profile?: WalkerProfile | WalkerProfileInput— Movement profile, complete or partial (merged over defaults).facing?: -1 | 1— Initial facing. Default 1.tags?: string[]— Tags.enabled?: boolean— Start disabled.
WalkerController
interface WalkerController
Something that drives or reacts to bodies every step (enemy AI, bots, rule packs).
readonly id: string— Controller id.think?(world: WalkerWorld, dt: number): void— Before bodies move: set intents.sense?(world: WalkerWorld, dt: number): void— After bodies move and sensors refresh: react.
WalkerEvent
type WalkerEvent = | { kind: "jumped"; id: string; x: number; y: number } | { kind: "dropped"; id: string; x: number; y: number } | { kind: "landed"; id: string; fall: number; fatal: boolean; on: "map" | "surface"; surface: string; col: number; row: number; x: number; y: number } | { kind: "support-lost"; id: string; cause: "edge" | "removed" | "teleported" | "inactive"; surface: string } | { kind: "bumped"; id: string; col: number; row: number; surface: string; x: number; y: number } | { kind: "ladder-enter"; id: string; col: number; at: "bottom" | "top" | "fall" | "side" } | { kind: "ladder-exit"; id: string; col: number; at: "top" | "bottom" | "side" | "jump" | "removed" } | { kind: "hang-start"; id: string; row: number } | { kind: "hang-end"; id: string; row: number; cause: "drop" | "end" | "removed" | "jump" | "climb" } | { kind: "wrapped"; id: string; axis: "x" | "y" } | { kind: "hazard"; id: string; col: number; row: number; surface: string } | { kind: "embedded"; id: string; col: number; row: number; crushedBy: string; cause: "surface" | "edit" | "overlap" } | { kind: "out-of-bounds"; id: string; side: "left" | "right" | "top" | "bottom" } | { kind: "bounced"; id: string; surface: string } | { kind: "stun-end"; id: string }
Events, in order, from drain().
WalkerIntent
interface WalkerIntent
What a body wants this step. Players map keys to it; controllers compute it.
x: number— -1 left .. 1 right.y: number— -1 up .. 1 down (ladders, dropping, flight).jump: boolean— Jump held. A press is detected from the previous step.
WalkerMode
type WalkerMode = "ground" | "air" | "climb" | "hang" | "fly" | "knocked"
Movement mode.
WalkerProfile
interface WalkerProfile
Everything about how one body moves. Serialisable; change at runtime with setProfile.
width: number— Body width, world units.height: number— Body height, world units.foot: number— Half-span sampled for support under the feet, world units (0: centre only; arcade bodies fall when the centre leaves).stepUp: number— Highest ledge stepped up onto while walking, world units (stepped girders).stepDown: number— Deepest drop followed while walking without falling, world units.walk: WalkProfile— Walking.jump: JumpProfile— Jumping.fall: FallProfile— Falling.climb: ClimbProfile— Ladders.hang: HangProfile— Bars.flight: FlightProfile— Flight.edge: "walk-off" | "stop"— At an edge: fall off ('walk-off') or refuse to step past it ('stop').wrap: boolean— Follow the map's wrap (Mario Bros sides, Bubble Bobble bottom-to-top).oneWayDrop: boolean— Down plus a jump press drops through a one-way floor.passPlatforms: boolean— Ignore one-way floors and ladder tops entirely (a barrel plunging down the screen).platform: boolean— Other bodies can stand on this one (trapped guards, frozen enemies).materials: Record<string, MaterialRule>— Ground rules per map material name.
WalkerProfileInput
type WalkerProfileInput = Partial<Omit<WalkerProfile, "walk" | "jump" | "fall" | "climb" | "hang" | "flight" | "materials">> & { /** Walking overrides. */ walk?: Partial<WalkProfile>; /** Jump overrides. */ jump?: Partial<JumpProfile>; /** Fall overrides. */ fall?: Partial<FallProfile>; /** Ladder overrides. */ climb?: Partial<ClimbProfile>; /** Bar overrides. */ hang?: Partial<HangProfile>; /** Flight overrides. */ flight?: Partial<FlightProfile>; /** Material rules (merged by name). */ materials?: Record<string, MaterialRule>; }
Any subset of a profile; nested groups merge field by field.
WalkerSensors
interface WalkerSensors
Sensor readings refreshed after every step; what AI needs to decide.
wallAhead: boolean— A wall directly ahead in the facing direction.edgeAhead: boolean— Grounded and no support under the leading edge ahead.ladderUp: boolean— A ladder that can be climbed upward from here (within grab range).ladderDown: boolean— A ladder that can be climbed downward from here.ladderX: number | null— Centre x of the nearest grabbable ladder, or null.barHere: boolean— Inside a bar cell.col: number— The cell the body occupies (feet row).row: number— The cell the body occupies (feet row).hitWall: boolean— Hit a wall horizontally this step.hitCeiling: boolean— Hit a ceiling this step.hitFloor: boolean— Landed or touched a floor this step (flight included).
WalkerSupport
interface WalkerSupport
What a body is standing on.
on: "map" | "surface"— A map cell or a provider surface.surface: string— Surface id whenonis 'surface'.col: number— Supporting cell column (map support).row: number— Supporting cell row (map support).kind: string— Support kind ('solid', 'platform', 'ladder-top', 'bound', or the surface kind).material: string— Material name ('' none).oneWay: boolean— One-way support (can be dropped through).
WalkerWorld
interface WalkerWorld
The hub of the platform mechanic layer. Create with createWalkerWorld.
readonly map: PlatformMap— The level.readonly dt: number— Fixed step, seconds.readonly time: number— Simulated seconds.readonly steps: number— Fixed steps run.add(def: WalkerBodyDef): string— Add a body; returns its id.remove(id: string): void— Remove a body.has(id: string): boolean— Whether a body exists.body(id: string): WalkerBody— A copy of a body's state. Throws for an unknown id.peek(id: string): Readonly<WalkerBody>— A body's live state, not a copy: cheap enough to read many times a step. Read it within the step; never keep it across steps or write to it (the world changes it as it steps). Throws for an unknown id.bodies(tag?: string): WalkerBody[]— Copies of every body, in insertion order, optionally filtered by tag.setIntent(id: string, intent: Partial<WalkerIntent>): void— Set a body's intent for the coming steps.setProfile(id: string, profile: WalkerProfileInput): void— Patch a body's profile.setSpeedScale(id: string, k: number): void— Speed multiplier (anger, hurry-up).setFacing(id: string, facing: -1 | 1): void— Face a direction.setTags(id: string, tags: string[]): void— Replace a body's tags.enable(id: string, on: boolean): void— Enable or disable a body (disabled bodies are frozen and invisible to sensors and riders).stun(id: string, seconds: number, o?: { flip?: boolean }): void— Stun: ignore intents forseconds(flipped or dazed); the body still falls and is carried. 0 clears.knock(id: string, o: { vx: number; vy: number; ghost?: boolean; seconds?: number; gravity?: number }): void— Knock: launch ballistically, ignoring intents until landing (or forseconds);ghostpasses through everything;gravitydefaults to the profile's (at least 300).teleport(id: string, x: number, feet: number): void— Move instantly (flaggedsnapped); the body settles onto support next step.grabLadder(id: string, col: number): boolean— Put a body onto a ladder column at its current height (scripted climbs, AI recovery).addProvider(p: SurfaceProvider): void— Add a surface provider.removeProvider(id: string): void— Remove a surface provider by id.addController(c: WalkerController): void— Add a controller.removeController(id: string): void— Remove a controller by id.surface(id: string): Surface | null— Surfaces seen in the current step (all providers plus platform bodies), by id.surfaces(): Surface[]— Surfaces seen in the current step.riders(): RiderMap— Surface id to rider body ids, from the end of the last step.overlaps(a: string, b: string, inset?: number): boolean— Two bodies' boxes overlap (wrap-aware), shrunk byinseton every side.query(left: number, top: number, right: number, bottom: number, tag?: string): WalkerBody[]— Bodies whose boxes overlap a rectangle (wrap-aware), optionally by tag.step(): void— One fixed step: providers, controllers think, bodies move, riders and sensors, controllers sense.tick(seconds: number): number— Run whole fixed steps for elapsed seconds; returns the number run.drain(): WalkerEvent[]— Events since the last drain.snapshot(): WalkerWorldSnapshot— Serialisable state.restore(s: WalkerWorldSnapshot): void— Restore a snapshot (providers and controllers restore their own).
WalkerWorldOptions
interface WalkerWorldOptions
World options.
map: PlatformMap— The level.providers?: SurfaceProvider[]— Surface providers, stepped in order before bodies move.controllers?: WalkerController[]— Controllers, run in order.step?: number— Fixed step, seconds. Default 1/60.maxSteps?: number— Most fixed stepstickruns per call (spiral-of-death guard). Default 8.
WalkerWorldSnapshot
interface WalkerWorldSnapshot
Serialisable world state (providers and controllers snapshot themselves).
bodies: BodyState[]— Bodies in insertion order (with private movement state).seq: number— Id counter.time: number— Simulated seconds.steps: number— Steps run.acc: number— Unconsumed tick time.riders: [string, string[]][]— Riders at the end of the last step.events: WalkerEvent[]— Undrained events.
WalkProfile
interface WalkProfile
Walking on the ground.
speed: number— Top walking speed, units per second.accel: number— Acceleration toward the target speed, units/s². 0 is arcade-instant (full speed at once).decel: number— Braking when the stick is released or reversed, units/s². 0 stops dead.turn?: number— Braking while the stick points against the motion (the skid when turning), units/s². Defaultdecel.


