The pack
platformMap
function platformMap(): Pack<{ platformMap: typeof api }>
Install the renderer-independent platform map API.
Helpers
CELL_BITS
const CELL_BITS: { readonly solid: 1; readonly platform: 2; readonly ladder: 4; readonly bar: 8; readonly hazard: 16; readonly broken: 32; readonly diggable: 64; readonly breakable: 128; readonly dug: 256; readonly through: 512; }
Bit flags stored per cell. Public API uses CellSpec/CellView; these are exported for compact snapshots.
createPlatformMap
function createPlatformMap(def: PlatformMapDef): PlatformMap
Build a platform map from strings or cell specs.
defaultLegend
function defaultLegend(): Record<string, LegendEntry>
The default legend. Characters can be replaced or extended per map.
girderMap
function girderMap(o: GirderMapDef): PlatformMapDef
Turn girder and ladder lines into a platform-map definition. Each tile-wide piece of a sloped girder keeps the standing height at its centre (rounded), so slopes become one-unit stairs like the arcade's; ladders run from the girder cell at their top to the girder cell at their bottom (snapped to the girders actually there), and a broken ladder's upper cells are marked broken so climbers cannot go down it or past its gap.
makeSurface
function makeSurface(fields: Pick<Surface, "id" | "left" | "right" | "top"> & Partial<Surface>): Surface
Fill a surface from the fields that matter, defaulting the rest (static, one-way, normal traction).
Types
BoundPolicy
type BoundPolicy = "solid" | "open"
How the map treats space outside its columns/rows on a side that does not wrap.
CellHit
interface CellHit
A solid cell hit by a rising head, or a horizontal wall.
col: number— Column.row: number— Row.at: number— The blocking line: the cell's bottom (ceiling) or the clamped x (walls).
CellSpec
interface CellSpec
What a cell is. Every field is optional; an empty spec is open air.
solid?: boolean— Blocks bodies from every side; its top is a standing surface.platform?: boolean— One-way (jump-through) platform: stand on the top, pass from below and the sides.ladder?: boolean— Climbable ladder. A ladder's top cell is standable whenladderTopFlooris on.broken?: boolean— A gap in a ladder: climbers whose profile blocks broken ladders cannot pass it (Donkey Kong).bar?: boolean— Hand-over-hand bar (Lode Runner): bodies that can hang move along it.hazard?: boolean— Touching it is reported as a hazard (spikes, fire, water).diggable?: boolean— Can be dug into a temporary hole withdig(Lode Runner bricks, Space Panic floors).breakable?: boolean— Can be removed withbreakCell.through?: boolean— Withsolid: a block that rising bodies pass through from below (no head bump, no embedding while passing), but that still blocks from the sides and is stood on from above. Bubble Bobble's platforms and inner walls.material?: string— Named material, e.g. 'ice', 'belt', 'brick'. Walkers look up traction by material.tag?: string— Free text owned by the game: 'rivet', 'trap', 'exit'. Not interpreted by the map.lift?: number— Standing surface lowered by this much inside the cell (stepped girders). 0..tile height.rise?: number— Extra lowering at the cell's right edge relative to its left (linear slope); negative rises.
CellView
interface CellView extends Required<Omit<CellSpec, "material" | "tag">>
A read-only view of one cell.
col: number— Column (wrapped into range when the map wraps).row: number— Row (wrapped into range when the map wraps).dug: boolean— A dug hole (open) that remembers the cell it was.empty: boolean— Nothing at all: no feature, material or tag.outside: boolean— Outside the map on a non-wrapping side.material: string— Material name or ''.tag: string— Tag or ''.
GirderLine
interface GirderLine
A girder (or conveyor) line: its standing line from (x1, y1) to (x2, y2), world units.
x1: number— One end, x.y1: number— Standing line at x1.x2: number— Other end, x.y2: number— Standing line at x2.material?: string— Cell material ('belt' for conveyors).
GirderMapDef
interface GirderMapDef
A level authored as girder and ladder lines (Donkey Kong's own layout format).
width: number— Width, world units (a multiple of the tile).height: number— Height, world units.tile?: number— Tile size. Default 8.girders: GirderLine[]— Girders: one-way platforms stepped one unit at a time along their slope.ladders: LadderLine[]— Ladders.solids?: { x: number; y: number; w: number; h: number }[]— Solid blocks, world units.tags?: { x: number; y: number; tag: string }[]— Tag the girder cell under a point (rivets).yis the girder's standing line.bounds?: PlatformMapDef["bounds"]— Map bounds. Default: solid sides, open top and bottom.brokenReach?: number— How far below the girder above a broken ladder's climb stops, world units (the ROM stops Jumpman 11 px short: $1D80). Default 11.
LadderLine
interface LadderLine
A ladder from a girder's standing line down to the girder below.
x: number— Any x inside the ladder's column.top: number— Standing line at its top.bottom: number— Standing line at its bottom.broken?: boolean— Broken: climbers whose profile blocks broken ladders cannot pass the gap (Jumpman), rollers can (barrels).
LadderRun
interface LadderRun
A vertical run of ladder cells.
col: number— Column.top: number— Top row of the run.bottom: number— Bottom row of the run.x: number— Centre x of the column.topY: number— World y of the run's top edge.bottomY: number— World y of the run's bottom edge.broken: number[]— Rows inside the run that are broken.
LegendEntry
interface LegendEntry extends CellSpec
A legend entry: a cell spec plus an optional named marker recorded at that cell.
marker?: string— Record an authored point with this name (start, enemy, item, exit, pipe ...).
MapChange
interface MapChange
One recorded edit.
revision: number— Map revision after this change.col: number— Column.row: number— Row.before: CellView— The cell before.after: CellView— The cell after.cause: string— Why: 'set', 'dig', 'refill', 'break', 'material' or a caller-supplied cause.
Marker
interface Marker
A named point recorded from the legend.
name: string— Marker name.col: number— Column.row: number— Row.x: number— Cell centre x.y: number— Cell centre y.floor: number— Bottom of the cell: where a body standing in the cell has its feet.
PlatformMap
interface PlatformMap
The level model. Create with createPlatformMap.
readonly cols: number— Columns.readonly rows: number— Rows.readonly tileW: number— Tile width, world units.readonly tileH: number— Tile height, world units.readonly x0: number— Left edge, world x.readonly y0: number— Top edge, world y.readonly width: number— Width in world units.readonly height: number— Height in world units.readonly wrapX: boolean— Columns wrap.readonly wrapY: boolean— Rows wrap.readonly bounds: Readonly<Record<"left" | "right" | "top" | "bottom", BoundPolicy>>— Bound policies.readonly ladderTopFloor: boolean— Whether ladder tops are floors.readonly revision: number— Increments on every edit.col(x: number): number— Column containing world x (not wrapped).row(y: number): number— Row containing world y (not wrapped).centre(col: number, row: number): { x: number; y: number }— Cell centre in world units.left(col: number): number— Cell's left edge, world x.top(row: number): number— Cell's top edge, world y.wrapWorldX(x: number): number— Wrap a world x into the map whenwrapX; otherwise unchanged.wrapWorldY(y: number): number— Wrap a world y into the map whenwrapY; otherwise unchanged.dx(a: number, b: number): number— Shortest signed x distance from a to b (across the wrap whenwrapX).dy(a: number, b: number): number— Shortest signed y distance from a to b (across the wrap whenwrapY).cell(col: number, row: number): CellView— The cell at a column/row (wrapped; outside cells followbounds).cellAt(x: number, y: number): CellView— The cell containing a world point.cells(): CellView[]— Every non-empty cell, row by row (for renderers).markers(name?: string): Marker[]— Markers, optionally by name, in row-major order.isSolid(col: number, row: number): boolean— Solid at a column/row (outside follows bounds).solidAt(x: number, y: number): boolean— Solid at a world point (respects lift: the part above a lowered surface is open).surfaceTop(col: number, row: number, x?: number): number | null— The standing height inside a cell at world x, or null.supportBelow(x: number, fromY: number, toY: number, o?: { span?: number; oneWay?: boolean; ignoreRow?: number | null }): Support | null— The highest standing surface crossed between two heights under a foot span centred on x.oneWay: falseignores platforms and ladder tops (dropping through);ignoreRowskips one row of one-way cells (the platform a body just dropped through).ceilingAbove(x: number, fromY: number, toY: number, span?: number, o?: { through?: boolean }): CellHit | null— The first solid cell bottom a head rising fromfromYtotoY(smaller) meets, centre column preferred.throughblocks are passed unlesso.through(flyers that bounce off them).sweepX(x: number, toX: number, halfWidth: number, top: number, bottom: number): { x: number; hit: CellHit | null }— Sweep a box horizontally; returns the reachable centre x and the wall hit, if any.ladderAt(x: number, y: number): LadderRun | null— The ladder run containing a world point, or null.ladderRun(col: number, row: number): LadderRun | null— The ladder run containing a cell, or null.ladderRuns(): LadderRun[]— Every ladder run, left to right then top to bottom.barAt(x: number, y: number): CellView | null— The bar cell at a world point, or null.hazardIn(left: number, top: number, right: number, bottom: number): CellView | null— The first hazard cell overlapping a rectangle, or null.solidIn(left: number, top: number, right: number, bottom: number): CellView | null— The first solid cell overlapping a rectangle, or null (throughblocks are never reported: bodies pass them).standable(col: number, row: number, o?: { ladders?: boolean; bars?: boolean }): boolean— A body can rest in this cell: open and supported below, or on a ladder/bar when allowed.lineOfWalk(row: number, c0: number, c1: number): boolean— Every cell from c0 to c1 on a row is open and standable (a body can walk the stretch).bodyCell(x: number, feetY: number): { col: number; row: number }— The cell a body standing with its feet at (x, feetY) occupies (the row its feet rest in).route(from: { col: number; row: number }, to: { col: number; row: number } | ((col: number, row: number) => boolean), o?: RouteOptions): RouteStep[] | null— Deterministic breadth-first route between cells, or null if unreachable.set(col: number, row: number, spec: CellSpec, cause?: string): void— Replace a cell.patch(col: number, row: number, partial: CellSpec, cause?: string): void— Change some fields of a cell.canDig(col: number, row: number): boolean— Whetherdigwould succeed: diggable, not already dug, inside the map.dig(col: number, row: number, cause?: string): boolean— Dig a diggable cell into an open hole that remembers it. Returns false if it cannot be dug.refill(col: number, row: number, cause?: string): boolean— Restore a dug hole. Returns false if the cell is not dug.breakCell(col: number, row: number, cause?: string): boolean— Remove a breakable cell. Returns false if it is not breakable.setMaterial(col: number, row: number, material: string, cause?: string): void— Change a cell's material ('' clears).original(col: number, row: number): CellView | null— What a dug cell was, or null.changesSince(revision: number): MapChange[]— Changes after a revision (oldest first). Throws if the history no longer reaches that far.drain(): MapChange[]— Changes since the previous drain.onChange(fn: (c: MapChange) => void): () => void— Call a function on every change. Returns an unsubscribe function. Listeners are not snapshotted.toRows(legend?: Readonly<Record<string, LegendEntry>>): string[]— Rows as strings through a legend (best match; '?' when nothing matches).snapshot(): PlatformMapSnapshot— Serialisable state.restore(s: PlatformMapSnapshot): void— Restore a snapshot from a map of the same size.
PlatformMapDef
interface PlatformMapDef
A map definition. Give rows (strings through a legend) or cells (specs).
rows?: readonly string[]— One string per row, top first; each character is looked up inlegend. Short rows are padded with open air.cells?: readonly (readonly CellSpec[])[]— Explicit cell specs,cells[row][col](alternative torows).legend?: Readonly<Record<string, LegendEntry>>— Character legend forrows. Defaults todefaultLegend().tile?: number | { w: number; h: number }— Tile size in world units: one number for square tiles. Default 8.origin?: { x: number; y: number }— World position of the map's top-left corner. Default (0, 0).wrapX?: boolean— Columns wrap: leaving the right edge re-enters on the left (Mario Bros).wrapY?: boolean— Rows wrap: falling out of the bottom re-enters at the top (Bubble Bobble).bounds?: Partial<Record<"left" | "right" | "top" | "bottom", BoundPolicy>>— Treatment of each non-wrapping side. Default: left/right solid, top/bottom open.ladderTopFloor?: boolean— A ladder's top cell (no ladder above it) is a standable floor. Default true.historyLimit?: number— How many changeschangesSincecan still report. Default 4096.
PlatformMapSnapshot
interface PlatformMapSnapshot
Serialisable state of a map.
bits: number[]— Cell flags.material: number[]— Material table indices.tag: number[]— Tag table indices.lift: number[]— Surface lifts.rise: number[]— Surface rises.materials: string[]— Material and tag string tables.tags: string[]— Tag strings.originals: [number, Stored][]— Dug cells' originals.revision: number— Revision.history: MapChange[]— Change history.drained: number— Drain cursor.
RiderMap
type RiderMap = ReadonlyMap<string, readonly string[]>
Surface ids to the walker body ids standing on them after the last walker step.
RouteMove
type RouteMove = "start" | "walk" | "climb-up" | "climb-down" | "hang" | "fall" | "drop" | "jump-up"
How a route moves between two cells.
RouteOptions
interface RouteOptions
What a router may use. Defaults suit a walker that climbs, hangs and drops any height.
climb?: boolean— Use ladders. Default true.hang?: boolean— Use hang bars. Default true.fall?: boolean— Walk off edges and drop from bars. Default true.maxFall?: number— Longest drop allowed, in rows. Default unlimited.jumpUp?: number— Jump up through one-way platforms this many rows above. Default 0 (never).ladderExit?: "ends" | "anywhere"— Step off a ladder sideways anywhere ('anywhere', Lode Runner) or only where it is standable ('ends'). Default 'anywhere'.brokenLadders?: boolean— Pass broken ladder cells. Default false.catchLadders?: boolean— Falling bodies catch ladders they fall into. Default true.limit?: number— Cap on explored cells. Default every cell.
RouteStep
interface RouteStep
One step of a route: the cell entered and how.
col: number— Column entered.row: number— Row entered (the row the body occupies: its feet rest on the row's bottom).move: RouteMove— How it is entered.
Support
interface Support
A standing surface found by supportBelow.
y: number— Standing line, world y.col: number— Column of the supporting cell.row: number— Row of the supporting cell.kind: "solid" | "platform" | "ladder-top" | "bound"— What supports: a solid cell, a one-way platform, a ladder's top, or the closed map bottom.material: string— Material of the supporting cell or ''.oneWay: boolean— True for one-way support (platform or ladder top).
Surface
interface Surface
One standable surface: a horizontal top edge in world units (y down), plus how it moves and treats riders.
id: string— Stable id, unique across every provider in a walker world. Riders are keyed by it.left: number— Left edge, world units.right: number— Right edge, world units.top: number— The standing line (top edge), world units, y down.height: number— Thickness belowtop; asolidsurface stops rising heads attop + height.dx: number— Horizontal displacement over the provider's last step; riders are carried by it.dy: number— Vertical displacement over the provider's last step; riders followtopexactly.vx: number— Horizontal velocity, units per second (informational; e.g. momentum on leaving).vy: number— Vertical velocity, units per second (informational).solid: boolean—false: one-way (stand on top, pass from below).true: also blocks a rising head (a bump).belt: number— Conveyor speed applied to riders, units per second, positive to the right. 0 for none.traction: number— Ground traction for riders: 1 normal, below 1 slippery (acceleration and braking scale by it).bounce: number— Launch speed (units per second, upward) given to a body that lands on it. 0 for none.bounceNeedsJump: boolean— Whenbounceis set, launch only while the rider holds jump (Bubble Bobble bubbles).hazard: boolean— Touching it is reported as a hazard event by the walker.active: boolean— Inactive surfaces are ignored (vanished timed platforms, fallen crumblers).teleported: boolean— Moved discontinuously during the last step (looping elevator): riders are released, not dragged.kind: string— Free classification for renderers and rules: 'path', 'elevator', 'belt', 'bubble', 'guard' ...owner: string— The provider (or game object) that owns it.riderTag?: string— Only bodies carrying this tag stand on it or bump it (e.g. 'player': bubbles that players ride while enemies pass through them). Omitted or '' means every body. Optional, so existing providers are unchanged.
SurfaceProvider
interface SurfaceProvider
A source of standable surfaces. The walker world calls step (if present) first in every fixed step with the
riders recorded at the end of the previous step, then reads surfaces() for that step's movement.
readonly id: string— Provider id (for diagnostics and removal).step?(dt: number, riders: RiderMap): void— Advance one fixed step. Optional: a provider whose surfaces are moved by someone else omits it.surfaces(): readonly Surface[]— The surfaces for the current step, in a stable order.


