Phaser AE

Excavation

Pro packv3.0.0-32DArcade

Diggable terrain for arcade digging games: earth that bodies tunnel through as they move, tunnels others can only follow, enemies that ghost through the dirt, and rocks that fall when undermined. It owns the ground and leaves the game rules to you.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 19, 2026

Excavation: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

excavation

function excavation(): Pack<{ excavation: ExcavationApi }>

Install the renderer-independent excavation API: diggable terrain, tunnelling bodies, falling objects.

Helpers

createExcavation

function createExcavation(config: ExcavationConfig): Excavation

Build a diggable-terrain field. Throws a readable error for an unsupported configuration.

EXCAVATION_LEGEND

const EXCAVATION_LEGEND: Readonly<Record<string, Terrain>>

The default map legend.

Classes

Excavation

class Excavation

A field of diggable terrain. Create one with createExcavation. Fixed-step and deterministic: the same config, the same inputs and the same step sequence produce the same state, and snapshot/restore round-trip it exactly.

  • readonly cols: number — Columns of cells.
  • readonly rows: number — Rows of cells.
  • readonly subdivisions: number — Sub-cells per cell edge.
  • readonly tunnelWidth: number — Default carve width as a fraction of a cell.
  • readonly strata: readonly number[] — Start rows of the strata, ascending.
  • readonly collectRadius: number — Pickup radius in cells.
  • constructor(config: ExcavationConfig)
  • inBounds(x: number, y: number): boolean — Whether a cell lies inside the field.
  • terrainAt(x: number, y: number): Terrain | null — The authored terrain of a cell (what it was built as; whether it is dug now is isOpen/dugFraction).
  • isBedrock(x: number, y: number): boolean — Bedrock or outside the field: nothing can dig it.
  • stratum(row: number): number — Index of the stratum (layer of earth) a row belongs to; -1 above the first stratum.
  • subcellDug(gx: number, gy: number): boolean — Whether sub-cell (gx, gy) is dug. Sub-cells are numbered from the field's top-left, subdivisions per cell.
  • isDugAt(x: number, y: number): boolean — Whether the point (x, y) in cell units lies in dug space. Outside the field is never dug.
  • dugFraction(x: number, y: number): number — Fraction of a cell's sub-cells that are dug (0..1).
  • isOpen(x: number, y: number): boolean — Whether a cell's centre is dug through: a body can stand in it and an object can fall into it.
  • connected(a: Cell, b: Cell): boolean — Whether two orthogonally adjacent cells are joined by dug earth along the line between their centres.
  • openings(x: number, y: number): Direction[] — Directions with a dug connection out of an open cell (ignores objects and bodies).
  • region(x: number, y: number): number — Connectivity region of an open cell: two cells share a region when a body could walk between them along dug connections. -1 for a cell that is not open. Recomputed lazily after digging.
  • distances(target: Cell): Int32Array — Tunnel distance in cells from every cell to target along dug connections (-1 where unreachable or not open). Cached until the terrain changes.
  • tunnelDistance(from: Cell, to: Cell): number — Tunnel distance between two cells, or -1 when one cannot walk from from to to.
  • route(from: Cell, to: Cell): Direction | null — The first step along the shortest dug route from from to to, or null when there is none.
  • nearestOpen(p: Cell, maxRadius = Math.max(this.cols, this.rows)): Cell | null — The nearest open cell to a point (by grid distance, ties in scan order), or null when nothing is dug.
  • carve(x0: number, y0: number, x1: number, y1: number): number — Dig every sub-cell whose centre lies in the rectangle [x0, x1] x [y0, y1] (cell units), except in bedrock. Returns how many sub-cells were newly dug.
  • digCell(x: number, y: number): number — Dig a whole cell (every sub-cell). Returns how many sub-cells were newly dug.
  • digTunnel(from: Cell, to: Cell, width = this.tunnelWidth): number — Dig a straight tunnel between two cell centres with a given width (default tunnelWidth).
  • fill(x0: number, y0: number, x1: number, y1: number): number — Refill the rectangle with earth (a hole closing up). Bedrock and sky cells are left alone. Returns sub-cells filled.
  • takeChanges(): Cell[] — Cells whose dug sub-cells changed since the last call (for a renderer redrawing only what changed).
  • addBody(spec: BodySpec): BodyState — Add a body. Digging bodies dig out the cell they start in.
  • removeBody(id: string): void — Remove a body (it died, escaped or left).
  • body(id: string): BodyState | undefined — A copy of one body's state, or undefined.
  • get bodies(): BodyState[] — Copies of every body, in update order.
  • steer(id: string, direction: Direction | null): void — Steer a held body: a direction moves it (cornering when the turn opens), null stops it.
  • want(id: string, direction: Direction): void — Ask an auto body to take a direction at its next opportunity (overrides one decision).
  • setSpeed(id: string, speed: number): void — Change speed (cells per second).
  • setFrozen(id: string, frozen: boolean): void — Freeze or release a body (a frozen body does not move).
  • setBlocking(id: string, blocking: boolean): void — Make a body block (or stop blocking) other grid bodies.
  • setFlags(id: string, flags: { collects?: boolean; pushes?: boolean; crushable?: boolean; control?: "held" | "auto"; digFactor?: number }): void — Set the remaining behaviour flags of a body.
  • setMode(id: string, mode: BodyState["mode"], ghost?: { target: Cell; policy?: GhostPolicy }): void — Change how a body moves. Entering ghost needs a target (updated later with ghostTarget); it lands by itself under policy and becomes tunnel again. Leaving ghost mode directly snaps it to its nearest cell.
  • ghostTarget(id: string, target: Cell): void — Move a ghost's target (a ghost chasing a moving player).
  • place(id: string, at: Cell, direction?: Direction): void — Put a body on a cell centre (a respawn): clears ghost, caught, crushed, frozen and steering.
  • exits(id: string): Direction[] — Directions a body could move from its nearest cell right now: dug connections for tunnel bodies, any diggable neighbour for diggers, never into bedrock, objects it cannot push or blocking bodies.
  • addObject(spec: { id: string; kind: string; at: Cell }, ids = new Set(this.objectList.map((o) => o.id))): ObjectState — Add a loose object at runtime (an apple a monster turned into).
  • removeObject(id: string): void — Remove a loose object (eaten by a monster).
  • get objects(): ObjectState[] — Copies of every loose object (gone ones are removed).
  • addItem(spec: { id: string; kind: string; at: Cell; group?: string }, ids = new Set(this.itemList.map((i) => i.id))): ItemState — Add a collectible at runtime (a bonus vegetable).
  • removeItem(id: string): void — Remove a collectible without collecting it (a bonus timing out, a monster eating it).
  • get items(): ItemState[] — Copies of the collectibles still in the field.
  • objectIn(x: number, y: number): ObjectState | undefined — The object resting (or wobbling, or crumbling) in a cell; falling objects are not in a cell.
  • step(dt: number, options: StepOptions = {}): void — Advance the field by dt seconds: bodies (in order), pushes, pickups, then loose objects.
  • get time(): number — Simulated seconds.
  • drain(): ExcavationEvent[] — Take the events since the last drain.
  • snapshot(): ExcavationSnapshot — A complete serializable copy of the field's mutable state.
  • restore(snap: ExcavationSnapshot): void — Restore a snapshot taken from a field built from the same config.

Types

BodyControl

type BodyControl = "held" | "auto"

Who drives a grid body. held: it moves only while steered (a player's held stick) and corners when the wanted turn opens. auto: it always moves at its speed and chooses a direction at every cell centre (the decide hook of step, or a seeded wander).

BodyMode

type BodyMode = "dig" | "tunnel" | "ghost" | "fixed"

How a body moves. dig: on the grid, carving earth as it goes (only bedrock, objects and blocking bodies stop it). tunnel: on the grid, only along dug connections between cells. ghost: freely through anything, towards a target, until it lands in an open cell (then it becomes tunnel again). fixed: does not move.

BodySpec

interface BodySpec

A body to add to the field.

  • id: string — Unique id.
  • kind?: string — Free-form kind for the game ("player", "pooka" ...). Default "body".
  • at: Cell — Start cell (its centre).
  • direction?: Direction — Initial heading. Default "left".
  • mode?: BodyMode — Movement mode. Default "tunnel".
  • control?: BodyControl — Steering. Default "auto".
  • speed: number — Speed in cells per second.
  • digFactor?: number — Speed multiplier while carving fresh earth (dig mode). Default 1.
  • carveWidth?: number — Width of the tunnel it carves, as a fraction of a cell. Default: the field's tunnelWidth.
  • turnBefore?: number — Cornering window before a cell centre, in cells (0 <= w < 0.5): a perpendicular turn wanted this far short of a turn line is taken at once, sliding onto the line. Default 0 (turns only exactly on a centre, as the arcade enemies do). A player usually wants about 0.4 so a turn pressed a little early is never deferred.
  • turnAfter?: number — Cornering window after a cell centre, in cells (0 <= w < 0.5): a turn wanted just past a line is taken there, sliding back onto it, rather than a whole cell later. Default 0.
  • collects?: boolean — Picks up collectibles it passes. Default false.
  • pushes?: boolean — Pushes pushable objects along tunnels when walking into them sideways. Default false.
  • crushable?: boolean — Can be caught and crushed by falling objects. Default true.
  • blocking?: boolean — Blocks other grid bodies from entering its cell. Default false.

BodyState

interface BodyState

Serializable state of one body.

  • id: string — Unique id.
  • kind: string — Free-form kind.
  • mode: BodyMode — Movement mode.
  • control: BodyControl — Steering.
  • x: number — Position in cell units (centre).
  • y: number — Position in cell units (centre).
  • direction: Direction — Heading (the last grid direction it moved or wants).
  • moving: boolean — Whether it moved during the last step.
  • speed: number — Speed in cells per second.
  • digFactor: number — Speed multiplier while carving fresh earth.
  • carveWidth: number — Carve width as a fraction of a cell.
  • turnBefore: number — Cornering window before a centre, in cells.
  • turnAfter: number — Cornering window after a centre, in cells.
  • steer: Direction | null — The held direction (held control) or null.
  • frozen: boolean — Frozen bodies do not move (stunned, inflated, caught).
  • blocking: boolean — Blocks other grid bodies.
  • collects: boolean — Collects items.
  • pushes: boolean — Pushes objects.
  • crushable: boolean — Can be crushed.
  • digging: boolean — Whether it carved fresh earth during the last step.
  • caughtBy: string | null — Id of the falling object carrying it, or null.
  • crushed: boolean — Set once an object has landed on it.
  • ghost: GhostState | null — Ghost state while in ghost mode.
  • walker: GridWalkerState — The grid walker's state (grid modes).

Cell

interface Cell

A cell, or a point in cell units.

  • x: number — Column (or x in cell units).
  • y: number — Row (or y in cell units), 0 at the top.

DecideContext

interface DecideContext

Context passed to a decide hook at a cell centre.

  • body: BodyState — The body deciding (a copy).
  • at: Cell — The cell it is centred on.
  • exits: Direction[] — Directions it can move from here (for tunnel bodies: dug connections; for diggers: any non-blocked cell).

Direction

type Direction = "up" | "left" | "down" | "right"

One of the four grid directions.

ExcavationApi

type ExcavationApi = typeof api

game.excavation: the diggable-terrain field (createExcavation), its legend and types.

ExcavationConfig

interface ExcavationConfig

Everything a field is built from.

  • map: string[] — The terrain, one string per row, all the same length. Default legend: # earth, space or . tunnel, ~ sky, = bedrock. Any number of columns and rows (up to the documented cell budget).
  • legend?: Record<string, Terrain> — Extra or replacement map characters.
  • subdivisions?: number — Sub-cells per cell edge (2..16). Default 4. Higher is a finer dig.
  • tunnelWidth?: number — Width a digging body carves, as a fraction of a cell (0 < w <= 1). Below 1, parallel tunnels keep a wall between them. Default 0.75.
  • strata?: number[] — Start row of each stratum (layer of earth), ascending. Default: one stratum from row 0.
  • tunnels?: TunnelSpec[] — Straight tunnels carved at start (as a body would dig them).
  • objectKinds?: Record<string, LooseObjectKind> — Loose object kinds.
  • objects?: ObjectSpec[] — Loose objects.
  • items?: ItemSpec[] — Collectibles.
  • collectRadius?: number — Radius in cells within which a collecting body picks an item up. Default 0.45.
  • seed?: number — Seed for the default wander policy. Default 1.

ExcavationEvent

type ExcavationEvent = | { kind: "dig"; id: string; cell: Cell; subcells: number; distance: number } | { kind: "collect"; item: string; itemKind: string; group: string; by: string; at: Cell } | { kind: "loosen"; object: string; at: Cell } | { kind: "fall"; object: string; at: Cell } | { kind: "catch"; object: string; body: string } | { kind: "land"; object: string; at: Cell; fell: number; broken: boolean; caught: string[] } | { kind: "remove"; object: string } | { kind: "push"; object: string; by: string } | { kind: "ghost"; id: string } | { kind: "materialise"; id: string; at: Cell }

Something that happened during a step. Drain them with drain().

ExcavationSnapshot

interface ExcavationSnapshot

A complete serializable snapshot of a field.

  • dug: string — Dug sub-cells, packed bits as hex.
  • bodies: BodyState[] — Bodies in update order.
  • objects: ObjectState[] — Loose objects.
  • items: ItemState[] — Collectibles still in the field.
  • random: RngState — The wander stream: the kernel generator's state (four words, from rng.snapshot()), so a restore repeats the draws.
  • time: number — Simulated seconds.

GhostPolicy

interface GhostPolicy

Where a ghost may land.

  • minSeconds?: number — Minimum seconds as a ghost before it may land. Default 0.5.
  • maxSeconds?: number — After this many seconds any open cell will do. Default: never relax.
  • landing?: "any" | "other-region" | "target-region" — any: any open cell. other-region: an open cell not connected to the tunnel it left. target-region: an open cell connected to the target's cell. Default other-region.
  • row?: number — Only land on this row (an escape to the surface).
  • keepAway?: number — Never land within this many cells of the target (eyes do not reappear on top of the player). Default 0.

GhostState

interface GhostState

Ghost state of a body in ghost mode.

  • target: Cell — The point it floats towards.
  • elapsed: number — Seconds spent as a ghost.
  • policy: GhostPolicy — The landing policy.
  • origin: number — Connectivity region of the tunnel it left (-1 when it left from earth).
  • landing: Cell | null — The open cell it is settling into, once chosen.

ItemSpec

interface ItemSpec

A collectible embedded in the earth (or lying in a tunnel).

  • id: string — Unique id.
  • kind: string — Free-form kind ("cherry", "gold", "vegetable").
  • at: Cell — Cell it sits in.
  • group?: string — Optional group id (a Mr. Do! cherry cluster).

ItemState

interface ItemState

Serializable state of one collectible.

  • id: string — Unique id.
  • kind: string — Free-form kind.
  • x: number — Cell it sits in.
  • y: number — Cell it sits in.
  • group: string — Group id or "".

LooseObjectKind

interface LooseObjectKind

Behaviour of one kind of loose object (a rock, an apple, a boulder).

  • wobbleSeconds?: number — Seconds it wobbles after losing support before it falls. Default 0.5.
  • fallSpeed?: number — Fall speed in cells per second. Default 6.
  • heldBy?: string[] — Body kinds that hold it up: while one of them stands directly beneath it (the digger who undermined it), it does not start to wobble. Default none.
  • breakAfter?: number — Cells fallen at which landing breaks it. 0 always breaks (a Dig Dug rock crumbles wherever it lands); 2 breaks after falling more than its own height (a Mr. Do! apple); a negative value never breaks. Default 0.
  • crumbleSeconds?: number — Seconds a broken object stays (crumbling) before it is removed. Default 0.6.
  • pushable?: boolean — Bodies that push can shove it sideways along tunnels. Default false.
  • catchWidth?: number — Half-width of the catch zone in cells: bodies whose centre is closer than this horizontally are caught. Default 0.6.

ObjectPhase

type ObjectPhase = "rest" | "loose" | "falling" | "landed" | "gone"

Lifecycle of a loose object.

ObjectSpec

interface ObjectSpec

A loose object to place.

  • id: string — Unique id.
  • kind: string — Kind key into objectKinds.
  • at: Cell — Cell it rests in.

ObjectState

interface ObjectState

Serializable state of one loose object.

  • id: string — Unique id.
  • kind: string — Kind key.
  • x: number — Centre in cell units.
  • y: number — Centre in cell units.
  • phase: ObjectPhase — Lifecycle.
  • timer: number — Seconds left in the current phase (wobble or crumble).
  • fromY: number — The y where the current fall began.
  • fell: number — Cells fallen in the last fall.
  • caught: string[] — Bodies caught by the current fall.
  • broken: boolean — Whether it broke on landing.
  • pushedBy: string | null — Id of the body pushing it during the last step, or null.

StepOptions

interface StepOptions

Options for one step.

  • decide?: (ctx: DecideContext) => Direction | null — Chooses the heading of an auto body at each cell centre. Return a direction from exits, or null to let the field's seeded wander choose. Called in body order; deterministic if the hook is.

Terrain

type Terrain = "earth" | "tunnel" | "sky" | "bedrock"

What a map character means. earth is diggable and starts solid. tunnel starts dug the way a body digs (walls left between parallel tunnels) and joins every orthogonal tunnel/sky neighbour. sky starts completely clear (a surface row). bedrock is solid and can never be dug.

TunnelSpec

interface TunnelSpec

A pre-dug straight tunnel between two cells in the same row or column.

  • from: Cell — One end.
  • to: Cell — The other end.