The pack
platformEnemies
function platformEnemies(): Pack<{ platformEnemies: typeof api }>
Install the renderer-independent platform enemies API.
Helpers
alienKind
function alienKind(o: KindOptions = {}): EnemyKind
Space Panic alien: wanders floors and ladders toward the player; cannot jump. Estimates.
barrelKind
function barrelKind(o: KindOptions & { ladderChance?: number; steer?: number; plungeChance?: number } = {}): EnemyKind
Donkey Kong barrel: rolls along girders (~1 px/frame, estimate), falls off the ends with a small bounce and reverses, and at each ladder top may roll down the ladder. The chance rises when Mario is below (the arcade steers barrels toward the player's joystick; modelled as a target-below bonus). Passes broken ladders.
bouncerKind
function bouncerKind(o: KindOptions = {}): EnemyKind
Bubble Bobble Monsta style flyer: flies diagonally and ricochets off walls, floors and ceilings. Estimates.
bubbleWalkerKind
function bubbleWalkerKind(o: KindOptions & { skill?: number } = {}): EnemyKind
Bubble Bobble walker (Zen-chan style): chases along floors, jumps up through one-way platforms toward a higher player, walks off edges toward a lower one, wraps from the bottom to the top. Estimates.
chaserKind
function chaserKind(o: KindOptions & { skill?: number } = {}): EnemyKind
A generic ladder-aware chaser for custom levels: shortest routes over floors and ladders, re-planned often.
createEnemies
function createEnemies(o: EnemiesOptions): EnemyDirector
Create an enemy director over a walker world.
fighterFlyKind
function fighterFlyKind(o: KindOptions = {}): EnemyKind
Mario Bros Fighter Fly: hops in fixed arcs and can only be flipped while touching the floor. Estimates.
fireballKind
function fireballKind(o: KindOptions & { ladderChance?: number; bias?: number } = {}): EnemyKind
Donkey Kong fireball / Firefox: wanders along girders, turns at edges, climbs ladders (not broken ones), drifting toward Mario. Speeds are estimates (fireballs are slower than Mario).
guardKind
function guardKind(o: KindOptions & { skill?: number; strategy?: "scan" | "lode-runner" } = {}): EnemyKind
Lode Runner guard: runs slower than the runner, climbs, hangs and drops. strategy: 'scan' (default) chases with a
row-scan heuristic after Doug Smith's original; strategy: 'lode-runner' uses the 1983 decision itself
(lodeRunnerMove: same-floor dash, then left/right floor scans rating ladders and drops 100 + rows above the
runner, 200 + rows below him, distance on his row), falls centred into holes and steps off ladders into the air.
Speeds are estimates (speed scales them).
lodeRunnerCell
function lodeRunnerCell(map: PlatformMap, b: Pick<WalkerBody, "x" | "feet">): { col: number; row: number }
The grid cell the original code tracks: the column under the centre, the row whose cell the feet are nearest.
lodeRunnerIntent
function lodeRunnerIntent(map: PlatformMap, me: WalkerBody, move: LodeRunnerMove, occupied: (col: number, row: number) => boolean = () => false): Partial<WalkerIntent>
Turn a decision into a walker intent: step sideways (climbers first reach a row line), climb or drop, centring on the column before entering a ladder. A move into another guard's cell becomes a stop.
lodeRunnerMove
function lodeRunnerMove(map: PlatformMap, me: WalkerBody, target: WalkerBody, occupied: (col: number, row: number) => boolean = () => false): LodeRunnerMove
Decide a guard's next move toward a target with the 1983 rules. occupied(col, row) reports another guard in a
cell (guards stand on guards, and never step into a guard's cell).
patrollerKind
function patrollerKind(o: KindOptions & { width?: number; height?: number } = {}): EnemyKind
A generic patroller for custom levels: walks a ledge and turns at edges and walls.
shellcreeperKind
function shellcreeperKind(o: KindOptions = {}): EnemyKind
Mario Bros Shellcreeper: walks, falls off ledges, wraps, flips on one bump, recovers faster. Estimates.
sidestepperKind
function sidestepperKind(o: KindOptions = {}): EnemyKind
Mario Bros Sidestepper: the first bump angers it (faster), the second flips it. Estimates.
Types
BehaviourContext
interface BehaviourContext
What a custom behaviour sees.
world: WalkerWorld— The walker world.map: PlatformMap— The level.random(): number— Seeded random number in [0, 1).dt: number— Fixed step.time: number— World time.target(enemyId: string): WalkerBody | null— The nearest target body to an enemy, or null.
BounceMove
interface BounceMove
Fly diagonally and ricochet off walls, floors and ceilings (needs a flight profile). Bubble Bobble's Monsta.
type: "bounce"— Bounce.dy?: -1 | 1— Initial vertical direction (-1 up, 1 down). Default 1.edges?: "wrap" | "bounce"— At the map's left and right edges: 'wrap' with the map (default) or 'bounce' back (use a profile withwrap: false).
CustomMove
interface CustomMove
A behaviour registered by name with the director (behaviours option); the extension point for rule packs.
type: "custom"— Custom.name: string— Registered behaviour name.params?: Record<string, number | string | boolean>— Parameters passed to the behaviour.
EmitterDef
interface EmitterDef
A spawner: pipes, a thrower, an oil drum.
id: string— Unique id.points: EmitterPoint[]— Spawn points; cycled or picked at random.pick?: "cycle" | "random"— 'cycle' through points or pick at 'random'. Default 'cycle'.kinds?: { kind: string; weight: number }[]— Weighted kinds chosen at random.sequence?: string[]— Kinds in order (used beforekinds); loops when exhausted.every?: number | number[]— Seconds between spawns: one number or a cycling table. Default 3.jitter?: number— Random extra seconds 0..jitter per interval. Default 0.first?: number— Seconds before the first spawn. Default 0.max?: number— Most live enemies from this emitter. Default unlimited.total?: number— Stop after this many spawns. Default unlimited.manual?: boolean— Only spawn onemit(). Default false.active?: boolean— Start active. Default true.
EmitterPoint
interface EmitterPoint
A spawn point.
x: number— Centre x.feet: number— Feet y.facing?: -1 | 1— Facing of the spawned enemy. Default 1.
EnemiesOptions
interface EnemiesOptions
Options for createEnemies.
world: WalkerWorld— The walker world the enemies live in.kinds: Record<string, EnemyKind>— Enemy kinds by name.emitters?: EmitterDef[]— Emitters.exits?: ExitDef[]— Exits.seed?: number— Seed. Default 1.behaviours?: Record<string, EnemyBehaviour>— Custom behaviours by name.targets?: string[]— Target body ids.attach?: boolean— Register as a world controller. Default true.id?: string— Controller id. Default 'enemies'.speedScale?: number— Global speed multiplier (difficulty, hurry-up). Default 1.
EnemiesSnapshot
interface EnemiesSnapshot
Serialisable director state (the walker world snapshots the bodies).
enemies: EnemyState[]— Enemies.emitters: { id: string; timer: number; index: number; spawned: number; point: number; active: boolean }[]— Emitters.rng: RngState— Random stream state (the kernel'srng.snapshot()).seq: number— Id counter.targets: string[]— Targets.speedScale: number— Global speed.events: EnemyEvent[]— Undrained events.
EnemyBehaviour
interface EnemyBehaviour
A custom behaviour. Keep state in enemy.memory (serialisable) so snapshots capture it.
init?(ctx: BehaviourContext, enemy: EnemyState, body: WalkerBody, params: Record<string, number | string | boolean>): void— Called once at spawn.think(ctx: BehaviourContext, enemy: EnemyState, body: WalkerBody, params: Record<string, number | string | boolean>): Partial<WalkerIntent>— Return the intent for this step.sense?(ctx: BehaviourContext, enemy: EnemyState, body: WalkerBody, params: Record<string, number | string | boolean>): void— After movement: react to sensors.
EnemyDirector
interface EnemyDirector extends WalkerController
The enemy director: a walker controller plus spawning, hits and queries.
readonly enemies: EnemyState[]— Copies of every enemy.enemy(id: string): EnemyState | null— One enemy, or null.view(): readonly Readonly<EnemyState>[]— The live enemy list, not copies: cheap enough to read many times a step. Never keep it across steps or write to it; to remove enemies while going through it, go through a copy of the array ([...view()]).peek(id: string): Readonly<EnemyState> | null— One enemy's live state, or null; seeview.count(kind?: string): number— Live enemies (not waiting in an exit), optionally of one kind.setTargets(ids: string[]): void— Set the target body ids.spawn(kind: string, at: { x: number; feet: number }, o?: { facing?: -1 | 1; emitter?: string }): string— Spawn an enemy at a feet position. Returns its id.emit(emitter: string, kind?: string): string | null— Spawn from an emitter now (its next point and kind unlesskindis given). Returns the id or null when capped.setEmitter(id: string, active: boolean): void— Turn an emitter on or off.hit(id: string): "flipped" | "angered" | "unflipped" | "ignored"— Hit an enemy: flips, angers, rights a flipped one, or is ignored.bump(col: number, row: number, radius?: number): { id: string; result: "flipped" | "angered" | "unflipped" | "ignored" }[]— Hit every enemy standing on a cell (andradiuscells either side): a floor bumped from below.pow(): { id: string; result: "flipped" | "angered" | "unflipped" | "ignored" }[]— Hit every grounded enemy (a POW block).flip(id: string, seconds?: number): void— Flip an enemy for a time regardless of hit rules.recover(id: string): void— Recover a flipped enemy now (no speed-up).suspend(id: string, reason: string): void— Hand an enemy to a rule pack (trapped in a bubble or a hole): no intents until resumed.resume(id: string): void— Return a suspended enemy to its behaviour.remove(id: string, reason?: string): void— Remove an enemy and its body.touching(bodyId: string, inset?: number): EnemyState[]— Enemies overlapping a body (any status except waiting).setSpeedScale(k: number): void— Global speed multiplier.setSpeed(id: string, k: number): void— One enemy's own speed multiplier (anger, hurry-up), kept across the director's speed updates.remember(id: string, key: string, value: MemoryValue): void— Write a memory value.drain(): EnemyEvent[]— Events since the last drain.snapshot(): EnemiesSnapshot— Serialisable state.restore(s: EnemiesSnapshot): void— Restore a snapshot (restore the walker world too).
EnemyEvent
type EnemyEvent = | { kind: "spawned"; id: string; enemy: string; x: number; y: number; emitter: string } | { kind: "turned"; id: string; facing: -1 | 1 } | { kind: "ladder"; id: string; dir: "up" | "down" } | { kind: "jumped"; id: string } | { kind: "flipped"; id: string } | { kind: "angered"; id: string; hits: number } | { kind: "unflipped"; id: string } | { kind: "recovered"; id: string; level: number } | { kind: "exited"; id: string; exit: string } | { kind: "reentered"; id: string; emitter: string } | { kind: "suspended"; id: string; reason: string } | { kind: "resumed"; id: string } | { kind: "removed"; id: string; reason: string }
Events from drain().
EnemyKind
interface EnemyKind
An enemy kind: a movement profile, a locomotion strategy and optional modifiers.
profile: WalkerProfile | WalkerProfileInput— Walker profile (complete or partial over the defaults).move: MoveSpec— Locomotion.hop?: { every: number; jitter?: number; onlyMoveInAir?: boolean }— Hop every few seconds while grounded (Fighter Fly);onlyMoveInAirstands still between hops.jumpUp?: { rows: number; chance: number; every?: number }— Jump up through one-way floors toward a higher target (Bubble Bobble enemies).stun?: StunSpec— Flip and recover.speedUp?: { alone: number; factor: number }— Speed multiplier when this many or fewer enemies remain (Mario Bros' last enemy).exits?: boolean— Use the director's exits (pipes). Default true.tags?: string[]— Extra tags on the walker body.
EnemyState
interface EnemyState
Public enemy state.
id: string— Walker body id.kind: string— Kind name.status: EnemyStatus— Status.hits: number— Hits taken toward the next flip.level: number— Recoveries so far (speed level).speed: number— Speed multiplier from anger and recoveries.timer: number— Seconds left flipped / waiting.emitter: string— Emitter it came from ('' if spawned directly).reason: string— Why suspended ('' otherwise).memory: Record<string, MemoryValue>— Behaviour memory.plan: RouteStep[]— Current route (pursuit).
EnemyStatus
type EnemyStatus = "active" | "stunned" | "suspended" | "waiting"
Enemy status.
ExitDef
interface ExitDef
A zone that swallows enemies and returns them from an emitter point after a delay (Mario Bros pipes).
id: string— Unique id.zone: { x: number; y: number; w: number; h: number }— Rectangle (top-left and size).to: string— Emitter to re-enter from.point?: number— Index of the emitter point to use; default cycles.delay?: number— Seconds out of play. Default 1.
IdleMove
interface IdleMove
Do nothing (items carried by belts and moving platforms, pies, cement tubs).
type: "idle"— Idle.
KindOptions
interface KindOptions
Options shared by the kind presets.
tile?: number— Level tile size; speeds and sizes scale by tile / 8. Default 8.speed?: number— Speed multiplier (difficulty, level). Default 1.
LodeRunnerMove
type LodeRunnerMove = "left" | "right" | "up" | "down" | "stop" | "fall"
A Lode Runner guard decision.
MemoryValue
type MemoryValue = number | string | boolean | null
Serialisable memory values for behaviours.
MoveSpec
type MoveSpec = PatrolMove | PursueMove | WanderMove | RollMove | BounceMove | IdleMove | CustomMove
Locomotion strategy.
PatrolMove
interface PatrolMove
Walk one way and turn at walls (and at edges when edges is 'turn'). Mario Bros enemies, generic patrols.
type: "patrol"— Patrol.edges?: "turn" | "fall"— At an edge: turn back, or walk off and fall. Default 'turn'.turnChance?: number— Chance per second of turning round for no reason. Default 0.
PursueMove
interface PursueMove
Chase a target over floors, ladders, bars and drops.
type: "pursue"— Pursue.strategy?: "path" | "scan" | "greedy" | "lode-runner"— 'path' (shortest route over the map), 'scan' (Lode Runner style: scan the row for the ladder or drop that gets closest to the target's row), 'greedy' (head for the target, climb when a ladder points the right way), 'lode-runner' (the 1983 guard decision itself, re-evaluated every step: seelodeRunnerMove). Default 'path'.replan?: number— Seconds between re-plans. Default 0.25.skill?: number— 0..1: chance per re-plan of making the good decision; otherwise wander for that interval. Default 1.climb?: boolean— Use ladders (if the profile can climb). Default true.hang?: boolean— Use bars (if the profile can hang). Default true.drop?: boolean— Walk off edges and drop from bars to reach the target. Default true.maxFall?: number— Longest drop in rows the route may plan. Default unlimited.jumpUp?: number— Rows the enemy may jump up through one-way platforms (needs a jumping profile). Default 0.unreachable?: "toward" | "wander" | "nearest"— 'path' with no route to the target: head toward its x on the current floor ('toward', default), roam floors and ladders until a route opens ('wander'), or plan to the nearest reachable cell on the target's row and press toward it from there ('nearest': chasers walk into the pits a player digs around himself).
RollMove
interface RollMove
Roll along floors, fall off ends, maybe take ladders down (Donkey Kong barrels).
type: "roll"— Roll.ladderChance?: number— Base chance of going down a ladder top it rolls over. Default 0.25.steer?: number— Extra chance when the target is below (the arcade's steering). Default 0.5.reverseOnLand?: boolean— Reverse after landing from a fall. Default true.afterLadder?: "reverse" | "keep" | "toward-target"— Direction after leaving a ladder. Default 'reverse'.plungeChance?: number— Chance at spawn of plunging straight through one-way floors to the bottom (wild barrels). Default 0.plungeHop?: number— While plunging: hop up at this speed (units/s) each time a one-way floor is crossed, so the barrel bounces down the screen (Donkey Kong wild barrels). Default 0 (fall straight through).plungeSpeed?: [number, number]— While plunging: horizontal speed range (units/s) chosen at spawn and at every floor crossed. Default [0, 0].plungeAim?: "random" | "target" | "proportional"— While plunging: direction of that speed: 'random', 'target' (toward the target), or 'proportional' (the target's x distance per second, capped at the range's maximum). Default 'random'.plungeUntil?: number— Stop plunging once the feet pass this world y: the next floor is landed on and rolled along. Default: plunge until a solid floor.plungeExit?: -1 | 0 | 1— Facing after a plunge ends (-1, 1, or 0 to keep). Default 0.
StunSpec
interface StunSpec
Flip-over / stun-and-recover rules (Mario Bros).
hits?: number— Hits needed to flip (Sidestepper: 2). Default 1.recover: number— Seconds flipped before recovering.groundedOnly?: boolean— Only hittable while touching the ground (Fighter Fly). Default false.recoverSpeedUp?: number— Speed multiplier gained on each recovery. Default 1.25.maxLevel?: number— Most recoveries that speed it up. Default 3.angerOnHit?: number— Speed multiplier from a hit that does not flip (an angry Sidestepper). Default 1.3.unflipOnHit?: boolean— Hitting a flipped enemy rights it. Default true.pop?: number— Upward pop speed when flipped. Default 0.
WanderMove
interface WanderMove
Roam floors and ladders, drifting toward the target (Donkey Kong fireballs, Space Panic aliens).
type: "wander"— Wander.ladderChance?: number— Chance of taking a ladder met on the way (once per ladder visit). Default 0.5.bias?: number— 0..1: how often decisions favour the target's direction. Default 0.5.turnChance?: number— Chance per second of turning round. Default 0.3.edges?: "turn" | "fall"— At an edge: turn or fall. Default 'turn'.


