Phaser AE

Missile defence

Pro packv3.0.0-32DArcade

A missile interception game as rules: batteries fire at a crosshair, explosions grow and chain, and waves of missiles, splitting warheads, smart bombs and fliers fall on the cities you protect. The 1980 arcade game is the preset, and the batteries, targets, waves and scoring are all yours to change.

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

Released October 14, 2026 · updated October 20, 2026

Missile defence: 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

missileDefence

function missileDefence(): Pack<{ missileDefence: typeof api }>

Install the renderer-independent missile defence rules (Missile Command and its variants) as game.missileDefence.

Helpers

createMissileDefence

function createMissileDefence(config: MissileDefenceConfig)

Create an independent missile defence game. No renderer or kernel clock is required.

explosionRadii

function explosionRadii(o: { radius: number; grow: number; hold?: number; shrink: number }): number[]

Build an explosion radius table: grow to radius over grow steps, hold hold steps, shrink over shrink.

MISSILE_COMMAND_EXPLOSION

const MISSILE_COMMAND_EXPLOSION: ExplosionDef

The arcade's explosion radius table (ROM $54B7): 0, 0, 2..13, 13..1, 0, stepped every 5 frames.

missileCommandPreset

function missileCommandPreset(o: MissileCommandPresetOptions = {}): MissileDefenceConfig

Missile Command (Atari, 1980) as data. Values from the annotated Rev 3 ROM disassembly (6502disassembly.com): a 256 x 231 field; bases Alpha/Delta/Omega at x 20/123/240 with 10 ABMs each per wave, side ABMs 3 units/frame and the centre's 7; at most 8 ABMs in flight; six cities at the ROM's positions; up to 8 attack slots (smart bombs cost two, at most three), launched four at a time once the highest is below altitude 202 - 2 x wave (at least 180); MIRVs split an ICBM in the 128-159 altitude band into up to three more; fliers from wave 2 (bombers 1/3, satellites 1/2 unit/frame) firing between x 48 and 208; octagonal explosions stepping the ROM radius table every 5 frames, chaining, never testing below altitude 33; 25/125/100 points, 5 per ABM and 100 per city left, multiplier 1x-6x every two waves; at most 3 cities lost per wave; a bonus city every 10,000.

missileCommandWaves

function missileCommandWaves(height = 231): WaveDef[]

The arcade's wave table (ROM $6090 ICBM counts, $60A3/$60B2 move delays, $60C1 smart bombs, flier tables $60D4/$60DB and the altitude bands), converted to y-down units on the 231-line field.

octagonDistance

function octagonDistance(dx: number, dy: number): number

The arcade's octagonal distance: max(|dx|, |dy|) + 3/8 min(|dx|, |dy|).

octagonPoints

function octagonPoints(x: number, y: number, r: number): MdPoint[]

The outline of an octagonal explosion of radius r (eight points, clockwise from +x), for drawing.

rampValue

function rampValue(start: number, step: number, limit: number, wave: number): number

Linear ramp over waves: start + step * (wave - 1), clamped to limit (an upper limit when step > 0, lower when < 0).

rampWaves

function rampWaves(o: RampWavesOptions): WaveDef[]

Generate a wave table from linear ramps: a quick way to author a difficulty curve for a variant.

validateMissileDefence

function validateMissileDefence(c: MissileDefenceConfig)

Throw a clear error for an unsupported definition. Called by the constructor.

Classes

MissileDefence

class MissileDefence

Owns a missile defence game: batteries, targets, incoming waves, explosions, scoring, bonus targets and the tally.

  • readonly config: MissileDefenceConfig — The validated definition (a private copy).
  • constructor(config: MissileDefenceConfig)
  • get state(): MissileDefenceState — Live state (read only).
  • reset(wave = 1) — Start a new game at wave (default 1).
  • waveDef(wave = this.s.wave): WaveDef — The wave definition used for wave (default the current one).
  • multiplier(wave = this.s?.wave ?? 1): number — The score multiplier for wave (default the current one).
  • get ammo(): number — ABMs left across all standing batteries.
  • get slotsUsed(): number — Attack slots in use (smart bombs count slotCost).
  • clampAim(p: MdPoint): MdPoint — Clamp a point to the crosshair limits.
  • aim(p: MdPoint) — Put the crosshair at a point (clamped).
  • pickBattery(order: FireOrder = "auto", at: MdPoint = this.s.cursor): { battery: number } | { reason: FireRefusal; battery: number | null } — The battery index an order resolves to for a launch at at, or a refusal.
  • fire(order: FireOrder = "auto", at?: MdPoint): FireResult — Launch an ABM at the crosshair (or at) now. Only while the attack is on.
  • tick(seconds: number, input: MissileDefenceInput = {}) — Advance by real seconds (fixed frames inside); aim and fire orders apply once, first. Returns the state.
  • step(input: MissileDefenceInput = {}) — Advance exactly one simulation frame.
  • predict(attack: AttackState, frames: number): MdPoint — Where an attack will be after frames frames on its current heading (straight-line estimate).
  • interceptPoint(attack: AttackState, battery: number, lead = 0): { x: number; y: number; frames: number } | null — Aim point for battery battery to meet attack lead frames after detonation (null if outside the crosshair limits).
  • chooseShot(o: { lead?: number; cover?: number } = {}): DefenceShot | null — A competent defender: the lowest attack not already covered by an ABM or a growing explosion, met by whichever battery detonates soonest, aimed so the attack arrives as the blast grows. Null = hold fire.
  • drain() — Take the events since the last drain.
  • snapshot() — Serializable snapshot of the whole game.
  • restore(snap: ReturnType<MissileDefence["snapshot"]>) — Restore a snapshot taken from the same configuration.

Types

AbmState

interface AbmState

One ABM in flight.

  • id: number — Stable id.
  • battery: string — Launching battery id.
  • ox: number — Launch point (the trail starts here).
  • oy: number — Launch point y in model units (y down).
  • x: number — Head.
  • y: number — Head y in model units (y down).
  • tx: number — Detonation point (the arcade's flashing X).
  • ty: number — Detonation point y in model units (y down).
  • speed: number — Units per frame.

AttackKind

type AttackKind = "icbm" | "smart"

An ICBM or a smart bomb.

AttackState

interface AttackState

One incoming attack.

  • id: number — Stable id.
  • kind: AttackKind — ICBM or smart bomb.
  • ox: number — Where it was launched (the trail starts here).
  • oy: number — Launch point y in model units (y down).
  • x: number — Head.
  • y: number — Head y in model units (y down).
  • tx: number — Aim point.
  • ty: number — Aim point y in model units (y down).
  • target: string | null — Target or battery id it is aimed at, or null for open ground.
  • source: "top" | "mirv" | "flier" — Launched from the top, split from a MIRV, or dropped by a flier.
  • canSplit: boolean — May still split.
  • evading: boolean — A smart bomb dodging this frame.

BatteryDef

interface BatteryDef

One ABM launcher (an arcade "base" or silo).

  • id: string — Stable id, also used as a fire order. Must not be "auto".
  • x: number — Centre x.
  • y: number — Ground level at the battery: an ICBM aimed at it lands here.
  • top: number — Launch height: ABMs leave from (x, top).
  • ammo: number — ABMs per wave. Original: 10 at each of three bases.
  • speed: number — ABM speed in units per frame. Original: 3 at the side bases, 7 at the centre.
  • explosion?: ExplosionDef — This battery's explosion, overriding explosions.abm.

BatteryState

interface BatteryState

One ABM battery now.

  • id: string — Battery id.
  • x: number — Centre x.
  • y: number — Ground y.
  • top: number — Launch y.
  • ammo: number — ABMs left.
  • alive: boolean — Not destroyed this wave.

DefenceShot

interface DefenceShot

A shot the defence bot recommends.

  • x: number — Aim point (inside the crosshair limits).
  • y: number — Aim point y in model units (y down), inside the crosshair limits.
  • battery: number — Battery index.
  • attack: number — Attack (or -1 for the flier) it is meant for.
  • frames: number — Frames until the ABM detonates.

ExplosionDef

interface ExplosionDef

An explosion: a radius table stepped every framesPerStep frames. It ends after the last entry.

  • radii: number[] — Radius per step. Original: 0, 0, 2..13, 13..0 (28 entries, max 13).
  • framesPerStep: number — Frames between steps (and between kill tests). Original: 5.

ExplosionState

interface ExplosionState

One explosion.

  • id: number — Stable id.
  • x: number — Centre.
  • y: number — Centre y in model units (y down).
  • radius: number — Current radius.
  • step: number — Index into the radius table.
  • age: number — Frames since it began.
  • table: number — -1 = the attack explosion, otherwise the battery index whose ABM made it.
  • source: "abm" | "attack" | "impact" | "flier" — What exploded.

FireOrder

type FireOrder = number | string

A fire order: a battery index, a battery id, or "auto" for the launch policy.

FireRefusal

type FireRefusal = "not-playing" | "no-ammo" | "destroyed" | "in-flight-limit" | "unknown-battery"

Why a fire order did nothing.

FireResult

type FireResult = { ok: true; battery: string; abm: number } | { ok: false; reason: FireRefusal; battery: string | null }

Result of a fire order.

FlierKind

interface FlierKind

One kind of flier (bomber, killer satellite ...).

  • id: string — Kind id.
  • speed: number — Units per frame. Original: bomber 1/3, satellite 1/2.
  • score: number — Points (times the multiplier). Original: 100.
  • hitSize: number — Added to an explosion's radius for the kill test. Original: 6.
  • weight: number — Relative chance of this kind.

FlierState

interface FlierState

The flier crossing now.

  • kind: string — Kind id.
  • x: number — Centre.
  • y: number — Centre y in model units (y down).
  • dir: number — +1 right, -1 left.
  • fireTimer: number — Frames before it may fire.

FlierWave

interface FlierWave

Flier rules for one wave.

  • minY: number — Altitude band the flier crosses at (y range).
  • maxY: number — Largest y of the band in model units (y down); each flier crosses at a random y from minY to this. Must be at least minY.
  • cooldown: number — Frames after a flier leaves or dies before the next appears.
  • fireDelay: number — Frames between a flier's volleys (the first volley also waits this long).
  • burst: number — ICBMs per volley, each to a different target (a volley is a split from the flier: original up to 3). It still needs free slots and the wave budget.

MdPoint

type MdPoint = { … }

A point in model units, y down.

  • x: number
  • y: number

MissileCommandPresetOptions

interface MissileCommandPresetOptions

Options for missileCommandPreset: the classic values unless overridden.

  • cities?: number — Cities (6 = classic, at the ROM positions; other counts are spread evenly between the bases).
  • ammo?: number — ABMs per base per wave (10 = classic).
  • bonusCityEvery?: number | null — Bonus city every this many points (10,000 = classic default DIP; 8,000-20,000 or null = none).
  • mirvChance?: number — Chance an eligible launch splits a MIRV instead of launching from the top (1 = the arcade).
  • maxCityLossPerWave?: number | null — Cities lost per wave at most (3 = classic; null = no protection).
  • seed?: number — Seed.

MissileDefenceConfig

interface MissileDefenceConfig

Complete missile defence definition. Every original value is a preset in missileCommandPreset.

  • width: number — Playfield width in model units.
  • height: number — Playfield height in model units.
  • hz: number — Simulation frames per second.
  • seed?: number — Seed for spawn positions, targets, MIRVs, fliers and bonus rebuilds.
  • aim: { minX: number; maxX: number; minY: number; maxY: number; speed: number; startX: number; startY: number } — The crosshair: limits and keyboard speed (units per frame at full deflection).
  • batteries: BatteryDef[] — ABM batteries, in fire-button order.
  • targets: TargetDef[] — Protected targets.
  • launch: { /** Which battery an \"auto"` order uses: the nearest with ammo, or the one whose ABM arrives soonest. / policy: "nearest" | "fastest"; /* ABMs in flight at once. Original: 8. / maxInFlight: number; /* A battery reports `low-ammo` when a launch leaves this many or fewer. Original: 3 ("LOW"). */ lowAt: number; }` — ABM launch rules.
  • attacks: { /** Where top launches start. Original: altitude 222, any x. */ spawnY: number; spawnMinX: number; spawnMaxX: number; /** Attack slots in flight (a smart bomb costs \smartBomb.slotCost`). Original: 8. / maxActive: number; /* Attacks launched at once from the top. Original: 4. / perBatch: number; /* Plain ICBMs. / icbm: { score: number; hitSize: number }; /* Smart bombs: no trail, dodge explosions, never change target. / smartBomb: { score: number; hitSize: number; /* Attack slots each costs. Original: 2. / slotCost: number; /* At most this many in flight. Original: 3. / maxActive: number; /* Chance a launch is a smart bomb while ICBMs remain (afterwards every launch is). Original: 1/4. / launchChance: number; /* No smart bomb launches while this many attacks are in flight. Original: 5. / crowdLimit: number; /* Distance of the eight explosion probes. / scan: number; /* From this wave a bomb may dodge upward. Original: 9. / evadeUpFromWave: number; }; /* MIRV splitting, or null for none. / mirv: { /* First wave with splits. Original: 1 (the ROM's wave-1 test never fires). / fromWave: number; /* The split band, as y. Original: altitude 128-159. / minY: number; maxY: number; /* * How the candidate is found. `"running-max"` is the ROM: walking the attacks in slot order, an ICBM is a * candidate whenever the highest attack seen so far is in the band, whatever its own height. * `"in-band"`: the candidate itself must be in the band. / candidate: "running-max" | "in-band"; /* Each ICBM splits at most once (the ROM allows repeats). / once: boolean; /* New missiles per split, each to a different target. Original: up to 3. / maxChildren: number; /* Chance an eligible launch splits instead of launching from the top. Original: 1. / chance: number; /* No split while this many explosions are alive, or null. Original: 12. / explosionLimit: number | null; } | null; /* Explosions of destroyed or landed attacks hold an attack slot until they end. Original: true. / explosionsHoldSlots: boolean; /* Flier kinds and where they may fire. Original: bombers and satellites, firing between x 48 and 208. */ fliers: { kinds: FlierKind[]; fireMinX: number; fireMaxX: number }; }` — Incoming attack rules.
  • explosions: { /** Default ABM explosion (a battery may override it). */ abm: ExplosionDef; /** Explosions of destroyed attacks and impacts. */ attack: ExplosionDef; /** Kill test shape: the arcade's octagon (max + 3/8 min) or a circle. */ shape: "octagon" | "circle"; /** Explosion slots; the oldest is replaced when full. Original: 20. */ max: number; /** A destroyed attack or flier explodes too, so kills chain. Original: true. */ chain: boolean; /** Attacks lower than this y are never tested (the ground band). Original: altitude 33. */ safeBelowY: number; } — Explosion rules.
  • waves: WaveDef[] — Wave table; the last entry repeats.
  • multipliers: number[] — Score multiplier per wave; the last entry repeats. Original: 1,1,2,2,3,3,4,4,5,5,6.
  • scoring: { /** Points (times the multiplier) per ABM left at the end of a wave. Original: 5. */ abmBonus: number; /** A bonus target is banked every this many points, or null for none. Original: 10,000. */ bonusTargetEvery: number | null; } — Bonus scoring.
  • rules: { /** At most this many targets can be lost in one wave (the attack retargets batteries), or null for no cap. Original: 3. */ maxTargetLossPerWave: number | null; /** End the wave at once when no ABMs are left and nothing more can be lost (cap reached or no targets). Original: true. */ endWaveWhenDefenceless: boolean; /** End the wave (and so the game) the moment the last target falls with no bonus banked. Original: false. */ endWaveWhenNoTargets: boolean; /** Ammunition each new wave: refill to each battery's \ammo`, or add to what is left up to a cap. Original: refill. / replenish: "refill" | { add: number; max: number }; /* Destroyed batteries are rebuilt for each wave. Original: true. */ restoreBatteries: boolean; }` — Wave and survival rules.
  • timing: { readyFrames: number; abmTallyFrames: number; targetTallyFrames: number; tallyPauseFrames: number } — Frame counts of the wave intro and the end-of-wave tally.

MissileDefenceEvent

type MissileDefenceEvent = | { kind: "wave-start"; wave: number; multiplier: number } | { kind: "attack-start"; wave: number } | { kind: "abm-launch"; battery: string; x: number; y: number; tx: number; ty: number } | { kind: "fire-refused"; reason: FireRefusal; battery: string | null } | { kind: "low-ammo"; battery: string; ammo: number } | { kind: "battery-empty"; battery: string } | { kind: "explosion"; id: number; x: number; y: number; source: ExplosionState["source"] } | { kind: "attack-launch"; id: number; attack: AttackKind; source: AttackState["source"]; x: number; y: number } | { kind: "mirv"; parent: number; children: number[]; x: number; y: number } | { kind: "attack-destroyed"; id: number; attack: AttackKind; score: number; x: number; y: number } | { kind: "impact"; id: number; attack: AttackKind; x: number; y: number; target: string | null } | { kind: "target-destroyed"; target: string; x: number } | { kind: "battery-destroyed"; battery: string; x: number } | { kind: "flier-start"; flier: string; dir: number; y: number } | { kind: "flier-fire"; flier: string; count: number; x: number; y: number } | { kind: "flier-destroyed"; flier: string; score: number; x: number; y: number } | { kind: "flier-gone"; flier: string } | { kind: "wave-clear"; wave: number; abandoned: boolean } | { kind: "tally-abm"; battery: string; points: number } | { kind: "tally-target"; target: string; points: number } | { kind: "bonus-earned"; bank: number } | { kind: "target-rebuilt"; target: string } | { kind: "game-over"; wave: number; score: number }

Ordered notifications for presentation: drain() them each frame.

MissileDefenceInput

interface MissileDefenceInput

One tick or frame of player input.

  • aim?: MdPoint — Put the crosshair here (pointer).
  • move?: MdPoint — Move the crosshair: -1..1 per axis, times aim.speed units per frame (keys, stick).
  • fire?: FireOrder[] — Fire orders, applied once before the frames run.

MissileDefenceState

interface MissileDefenceState

Serializable game state; read it through state and do not mutate it.

  • frame: number — Frames since the game began.
  • wave: number — Wave number, from 1.
  • score: number — Score.
  • status: MissileDefenceStatus — Game status.
  • statusFrames: number — Frames left in ready/tally steps; frames since THE END in game-over.
  • multiplier: number — This wave's multiplier.
  • cursor: MdPoint — The crosshair.
  • batteries: BatteryState[] — Batteries, in config order.
  • targets: TargetState[] — Targets, in config order.
  • abms: AbmState[] — ABMs in flight.
  • attacks: AttackState[] — Attacks in flight.
  • explosions: ExplosionState[] — Explosions.
  • flier: FlierState | null — The flier, if one is crossing.
  • flierCooldown: number — Frames before the next flier.
  • remaining: { icbms: number; smartBombs: number } — Attacks this wave not yet launched.
  • lostThisWave: number — Targets lost this wave.
  • bonusBank: number — Bonus targets banked and not yet rebuilt.
  • bonusAwarded: number — Bonus thresholds passed so far.
  • tally: TallyState — Tally progress (meaningful in tally).
  • nextId: number — Next entity id.
  • random: number[] — The kernel generator's state (four words from createRng), advanced in place, so a snapshot carries the stream.

MissileDefenceStatus

type MissileDefenceStatus = "ready" | "playing" | "wave-end" | "tally" | "game-over"

Wave intro, attack, waiting for the last explosions, bonus tally, or THE END.

RampWavesOptions

interface RampWavesOptions

Options for rampWaves. Each ramp is [start, perWave, limit].

  • waves: number — Waves to generate (the last repeats forever).
  • icbms: [number, number, number] — ICBMs per wave.
  • speed: [number, number, number] — Attack speed, units per frame.
  • smartBombs?: [number, number, number] — Smart bombs per wave, from wave smartFrom (default none).
  • smartFrom?: number — First wave with smart bombs (default 1).
  • launchBelowY: [number, number, number] — launchBelowY per wave.
  • flier?: { from: number; minY: number; maxY: number; cooldown: [number, number, number]; fireDelay: [number, number, number]; burst: number } — Fliers from wave from (default none). Cooldown and fire delay ramp downward.

TallyState

interface TallyState

End-of-wave tally progress.

  • phase: "abms" | "targets" | "pause" — Counting ABMs, then targets, then a pause.
  • battery: number — Battery being counted.
  • target: number — Next target to count.
  • abms: number — ABMs counted.
  • targets: number — Targets counted.
  • points: number — Points awarded by this tally.
  • carry: number[] — Ammunition per battery when the wave ended (for carry-over replenishment).

TargetDef

interface TargetDef

One protected target (an arcade "city"). Losing every target ends the game.

  • id: string — Stable id.
  • kind?: string — Free-form kind for presentation (default "city").
  • x: number — Centre x.
  • y: number — Impact height: an ICBM aimed at it lands here.
  • width: number — Width (presentation and the bot's danger test).
  • bonus: number — Points (times the multiplier) for surviving a wave. Original: 100.

TargetState

interface TargetState

One target now.

  • id: string — Target id.
  • kind: string — Kind (presentation).
  • x: number — Centre x.
  • y: number — Impact y.
  • width: number — Width.
  • alive: boolean — Standing.

WaveDef

interface WaveDef

One wave of the attack. The last entry repeats for every later wave.

  • icbms: number — ICBMs this wave (top launches, MIRV children and flier volleys all draw on it).
  • smartBombs: number — Smart bombs this wave.
  • speed: number — Attack speed in units per frame (ICBMs and smart bombs). Original: 1 / (1 + delay).
  • launchBelowY: number — New attacks wait until the highest attack in flight is below this y. Original: altitude 202 - 2 x wave, at least 180.
  • flier: FlierWave | null — Flier rules, or null for no fliers. Original: none on wave 1.