The pack
thrustCraft
function thrustCraft(): Pack<{ thrustCraft: typeof api }>
Install the thrust craft API.
Helpers
closestOnSegment
function closestOnSegment(p: Vec2, a: Vec2, b: Vec2): { point: Vec2; dist: number; t: number }
The closest point on segment ab to p, with the distance.
createThrustWorld
function createThrustWorld(def: ThrustWorldDef): ThrustWorld
Build a thrust world.
fromUpright
function fromUpright(points: readonly Vec2[]): Vec2[]
Convert an outline drawn upright in screen space (nose toward -y) into the local nose-along-+x frame.
tileMaskSegments
function tileMaskSegments(mask: TileMask): Array<{ a: Vec2; b: Vec2; normal: Vec2 }>
Turn a tile mask into merged edge segments, each with its outward normal.
toWorld
function toWorld(p: Vec2, x: number, y: number, angle: number): Vec2
Rotate a local point into world space.
UP
const UP: number
The angle of a nose pointing straight up the screen (y down): -PI/2.
wrapAngle
function wrapAngle(a: number): number
Wrap an angle to (-PI, PI].
Functions
facingNormal
function facingNormal(seg: TerrainSegment, from: Vec2): Vec2
Unit normal of a segment facing the side from is on (for two-sided walls), or its fixed normal.
Classes
TerrainIndex
class TerrainIndex
The live terrain: chains and pads compiled to segments with a grid broadphase.
segments: TerrainSegment[]— Every live segment, ghosts included.constructor(private cell: number, private wrap: SeamWrap)build(chains: readonly TerrainDef[], pads: readonly PadDef[]): void— Rebuild from chains (enabled ones only) and pads.query(x0: number, y0: number, x1: number, y1: number): TerrainSegment[]— Segments whose cells overlap the box (each once).sweep(prev: Vec2, cur: Vec2, pad = 0): (SweepHit & { segment: TerrainSegment }) | null— Earliest crossing of the path prev -> cur with live terrain (vector-hit's swept test over the broadphase).raycast(from: Vec2, dir: Vec2, max: number): { point: Vec2; distance: number; segment: TerrainSegment } | null— Cast a ray in chunks so the broadphase stays local.heightAt(chain: string, x: number): number— Height of a heightfield chain at x (vector-terrain'sheightAtX); Infinity outside it or for other chains.
Types
AltitudeReading
interface AltitudeReading
Altitude above the surface below (along gravity).
distance: number— Distance from the lowest foot to the surface; Infinity when nothing is below.point: Vec2 | null— Where the ray met the surface.pad?: string— The pad below, if any.chain?: string— The chain below.
BoundsDef
interface BoundsDef
The world rectangle and what each side does.
x?: number— Left edge. Default 0.y?: number— Top edge. Default 0.width: number— Width in world units.height: number— Height in world units.edges?: { left?: EdgePolicy; right?: EdgePolicy; top?: EdgePolicy; bottom?: EdgePolicy }— Per-side policy. Default every sideopen. Wrapping needs both opposite sides set towrap.
ControlDef
type ControlDef = /** Turn input -1..1 at `turnRate` rad/s (with optional angular acceleration and quantised headings). */ | { mode: "rotate"; /** Turn rate at full input, rad/s (with `momentum`, the spin cap). */ turnRate: number; /** Angular acceleration, rad/s^2: without `momentum` the spin eases toward the input; with it the input accelerates the spin. */ turnAccel?: number; /** Rotational momentum: the spin persists after the input is released until countered (Lunar Lander's Command mission). Needs `turnAccel`. */ momentum?: boolean; /** With `momentum`, a released spin slower than this snaps to zero, rad/s. Default 0. */ spinDeadband?: number; /** Quantise the heading into this many directions. */ steps?: number; /** Largest tilt from local up the craft may turn to, radians (PI/2: never past horizontal). Default unlimited. */ limit?: number; } /** Turn toward an absolute `angle` intent at `turnRate` (Infinity snaps). */ | { mode: "direct"; turnRate?: number; steps?: number; limit?: number } /** No rotation: the craft keeps its angle and flies on `vector` thrusters. */ | { mode: "fixed" }
How a craft turns.
CraftDef
interface CraftDef
A craft's definition. Hull points are local, nose along +x (use fromUpright for outlines drawn upright).
id: string— Craft id.x: number— Start x.y: number— Start y.vx?: number— Start velocity.vy?: number— Start velocity.angle?: number— Nose angle, radians. Default UP (-PI/2).hull?: Vec2[]— Local hull outline (convex or concave), nose along +x. Default a triangle ofradius.feet?: number[]— Hull vertex indices that are landing feet. Default every vertex.radius?: number— Radius used for the default hull. Default 8.mass?: number— Mass (tethers share corrections by inverse mass). Default 1.control?: ControlDef— Steering. Default{ mode: 'rotate', turnRate: PI }.thrust?: number— Main engine acceleration at full throttle, along the nose. Default 0.throttle?: number[]— Discrete throttle fractions; input snaps to the nearest. Default continuous.vector?: VectorThrustDef— World-axis thrusters (Space Taxi).fuel?: FuelDef— Fuel tank. Omit for unlimited.drag?: Drag— Linear drag per second (or per axis). Default the world's drag.maxSpeed?: number— Terminal speed clamp.gravityScale?: number— Gravity multiplier. Default 1.landing?: LandingDef— Landing tolerances. Omit: no contact is a landing.bounce?: { restitution: number; friction?: number; maxSpeed: number }— Bounce instead of crashing on contacts slower thanmaxSpeedthat are not landings.gear?: { down?: boolean; time?: number; required?: boolean }— Landing gear.shield?: Record<string, never>— The craft has a shield (a state the rules interpret).tractor?: TractorDef— The craft has a tractor beam.data?: Record<string, unknown>— Free data for the rules pack.
CraftIntent
interface CraftIntent
One step's control of one craft.
turn?: number— Turn -1..1 (rotate mode).angle?: number— Target nose angle (direct mode).thrust?: number— Throttle 0..1.vector?: Vec2— Vector thrusters, each axis -1..1 (x right, y down).level?: boolean— Rotate toward local up (an abort's righting).levelRate?: number— Turn rate forlevel, rad/s. Default the craft's turn rate.shield?: boolean— Hold the shield.tractor?: boolean— Hold the tractor beam.gear?: "up" | "down" | "toggle"— Move the gear.release?: boolean— Drop the tethered load.boost?: { accel: number; burn?: number }— An extra engine firing this step along the nose regardless of the throttle (an abort burn, an afterburner): acceleration and fuel per second.
CraftState
interface CraftState
A craft's live state (plain data, snapshotted).
id: string— The craft's id from its definition.x: number— Position across the world, in world units.y: number— Position down the world, in world units; larger is lower.vx: number— Horizontal velocity, in units/s.vy: number— Vertical velocity, in units/s; positive is downward.angle: number— Nose angle used for thrust and hull (quantised whenstepsis set).heading: number— Unquantised heading.spin: number— Angular velocity, rad/s.status: "flying" | "landed" | "crashed" | "exited"— flying, landed, crashed or exited.rest: Rest | null— Rest details while landed.fuel: number— Fuel left (Infinity when unlimited).fuelMax: number— Tank size (Infinity when unlimited).throttle: number— Throttle applied this step 0..1.boost: number— Boost acceleration applied this step (0 when none).vector: Vec2— Vector thrust applied this step.shield: boolean— Shield up this step.tractor: boolean— Tractor on this step.gear: { down: boolean; t: number }— Gear target and deployment 0 (up) .. 1 (down).tether: { load: string; length: number; kind: "rod" | "rope" } | null— Tethered load.attach: { load: string; t: number } | null— Seconds the current load candidate has been in the beam.data: Record<string, unknown>— Free data copied from the definition.
CrashCause
type CrashCause = "speed" | "angle" | "gear" | "hull" | "lethal" | "edge" | "slope"
Why a craft crashed.
Drag
type Drag = number | Vec2
Linear drag per second: one rate for both axes, or one per axis ({ x, y }: Thrust damps sideways motion four times harder than vertical).
EdgePolicy
type EdgePolicy = "wrap" | "wall" | "exit" | "crash" | "open"
What happens to a body crossing one side of the world rectangle.
FuelDef
interface FuelDef
Fuel: absent means unlimited. Burns are units per second at full use.
capacity: number— Tank size.start?: number— Starting fuel. Default the capacity.burn?: number— Main engine burn at full throttle. Default 1.vectorBurn?: number— Vector thruster burn at full deflection. Defaultburn.turnBurn?: number— Burn while turning. Default 0.shieldBurn?: number— Burn while the shield is up. Default 0.tractorBurn?: number— Burn while the tractor beam is on. Default 0.
ItemDef
interface ItemDef
A fuel item the tractor (or a touch) takes.
id: string— Item id.x: number— Position.y: number— Position.radius?: number— Pickup radius. Default 10.fuel?: number— Fuel it holds. Default 0.tractor?: "drain" | "collect" | "none"— How the tractor takes it: drained over time, collected whole, or not at all. Default 'collect'.touch?: boolean— Collected when the hull touches it. Default false.data?: Record<string, unknown>— Free data.
ItemState
interface ItemState
An item's live state.
id: string— The item's id from its definition.x: number— Position across the world, in world units.y: number— Position down the world, in world units; larger is lower.fuel: number— Fuel the item still holds; a draining item loses it, and a collected one is gone.data: Record<string, unknown>— Free data copied from the definition.
LandingDef
interface LandingDef
Landing rules for a craft. Omit to make every contact a crash (or a bounce).
soft: LandingGrade— A soft (good) landing.hard?: LandingGrade— A hard landing: survived, but the rules may score it lower. Omit for no hard grade.settle?: boolean— Settle upright on the surface normal after landing. Default true.maxSlope?: number— Steepest landable slope onlandablechains (pads are always flat). Default 0.2 rad.footTolerance?: number— Every foot must be within this distance of a surface after touchdown, or it is a crash (causeslope): both legs on the ground. Default unchecked.
LandingGrade
interface LandingGrade
Contact tolerances for one landing grade.
normal: number— Highest approach speed along the surface normal.tangent: number— Highest slide speed along the surface.angle: number— Highest tilt from the surface normal, radians.
LoadDef
interface LoadDef
A tetherable load (Thrust's pod).
id: string— Load id.x: number— Position.y: number— Position.vx?: number— Velocity.vy?: number— Velocity.mass?: number— Mass. Default 1.radius?: number— Collision radius. Default 8.anchored?: boolean— Sits fixed (infinite mass) until a taut tether pulls it off. Default false.drag?: Drag— Linear drag per second (or per axis). Default the world's drag.gravityScale?: number— Gravity multiplier. Default 1.bounce?: number— Restitution against terrain. Default 0.3.data?: Record<string, unknown>— Free data.
LoadState
interface LoadState
A load's live state.
id: string— The load's id from its definition.x: number— Position across the world, in world units.y: number— Position down the world, in world units; larger is lower.vx: number— Horizontal velocity, in units/s.vy: number— Vertical velocity, in units/s; positive is downward.anchored: boolean— Whether it still sits fixed, as if of infinite mass, until a taut tether pulls it off.touching: boolean— Touching terrain this step.by: string | null— The craft towing it.data: Record<string, unknown>— Free data copied from the definition.
PadDef
interface PadDef
A flat landable pad.
id: string— Pad id, named by landing events.x: number— Centre x.y: number— Centre y (the pad's top surface).width: number— Width along the surface.normal?: number— Direction of the pad's outward normal, radians. Default -PI/2 (facing up).surface?: SurfaceProps— Extra surface properties (lethal, bounce, tag).landableis always true.data?: Record<string, unknown>— Free data for the rules pack (a multiplier, a pad number).
Rest
interface Rest
Where a landed craft rests.
quality: "soft" | "hard"— Landing grade.pad?: string— The pad id, if a pad.chain: string— The chain id (the pad id for a pad).normal: Vec2— Surface normal.point: Vec2— Contact point.
SeamWrap
interface SeamWrap
Wrap configuration the terrain needs to place seam ghosts.
x: boolean— Whether the world wraps horizontally, so its left and right edges meet.y: boolean— Whether the world wraps vertically, so its top and bottom edges meet.minX: number— The x of the world's left edge, in world units.minY: number— The y of the world's top edge, in world units.width: number— The world's width, in world units: how far a ghost is shifted across a horizontal seam.height: number— The world's height, in world units: how far a ghost is shifted across a vertical seam.margin: number— Width of the strip next to each seam whose segments are mirrored across it, in world units.
SurfaceProps
interface SurfaceProps
How a stretch of terrain behaves when a craft or load touches it.
landable?: boolean— A craft may land here (pads always may). Default false.maxSlope?: number— For landable chains: the steepest slope, in radians from level, a craft may land on. Default the craft'slanding.maxSlope.bounce?: number— Restitution used when a craft or load bounces here (0..1). Default the body's own.friction?: number— Friction used when a body bounces here (0..1). Default the body's own.lethal?: boolean— Any touch is a crash (force fields, lava). Default false.tag?: string— Free text for the rules pack (e.g. "door", "ice").
TerrainDef
interface TerrainDef
One terrain chain. Exactly one of points, heightfield or mask is given.
id?: string— Stable id (events and edits name it). Defaultterrain-<index>.points?: Vec2[]— An open polyline, or a closed outline withclosed.closed?: boolean— Close the outline back to its first point. Default false.heightfield?: Vec2[]— A heightfield polyline sorted by x (single-valued in x): ground below, sky above.mask?: TileMask— A tile grid; the exposed edges of solid cells become segments.surface?: SurfaceProps— Surface behaviour.enabled?: boolean— Start disabled (a closed door). Default true.
TerrainSegment
interface TerrainSegment extends Segment
A live terrain segment.
index: number— Index into the live segment list.chain: string— The chain id, or the pad id for a pad.pad?: string— The pad id when this segment is a pad.surface: SurfaceProps— Surface properties.normal?: Vec2— Unit normal for one-sided segments (pads, heightfields, masks); absent for two-sided polylines.ghost?: boolean— A seam ghost: a copy offset by the world width/height in a wrapping world.
ThrustEvent
type ThrustEvent = | { type: "landed"; craft: string; quality: "soft" | "hard"; pad?: string; chain: string; normalSpeed: number; tangentSpeed: number; tilt: number; at: Vec2 } | { type: "crashed"; craft: string; cause: CrashCause; pad?: string; chain?: string; normalSpeed: number; tangentSpeed: number; tilt: number; at: Vec2 } | { type: "bounced"; craft: string; pad?: string; chain: string; speed: number; at: Vec2 } | { type: "took-off"; craft: string; pad?: string; chain: string } | { type: "fuel-empty"; craft: string } | { type: "exited"; craft?: string; load?: string; edge: "left" | "right" | "top" | "bottom" } | { type: "wrapped"; craft?: string; load?: string; axis: "x" | "y" } | { type: "gear"; craft: string; down: boolean } | { type: "tethered"; craft: string; load: string; length: number } | { type: "released"; craft: string; load: string } | { type: "load-lifted"; load: string; craft: string } | { type: "load-hit"; load: string; speed: number; chain: string; at: Vec2 } | { type: "item-drained"; craft: string; item: string; fuel: number } | { type: "item-collected"; craft: string; item: string; fuel: number }
Everything the world reports.
ThrustSnapshot
interface ThrustSnapshot
Everything needed to restore a world, into the same world or a fresh one built from any definition.
time: number— Simulated seconds.tick: number— Steps taken.gravity: Vec2— Uniform gravity.wells: WellDef[]— Point wells.crafts: CraftState[]— Live craft states.loads: LoadState[]— Live load states.items: ItemState[]— Live item states.terrain: TerrainDef[]— Terrain chains, as edited.pads: PadDef[]— Landing pads.craftDefs: CraftDef[]— The current definition of every craft incrafts, as changed byaddCraftandupdateCraft.loadDefs: LoadDef[]— The definition of every load inloads.itemDefs: ItemDef[]— The definition of every item initems.
ThrustWorld
interface ThrustWorld
A created thrust world.
readonly def: ThrustWorldDef— The definition it was built from.readonly dt: number— Fixed step, seconds.readonly time: number— Simulated seconds.readonly tick: number— Steps taken.readonly crafts: readonly CraftState[]— Live crafts.readonly loads: readonly LoadState[]— Live loads.readonly items: readonly ItemState[]— Live items.readonly pads: readonly PadDef[]— Live pads.step(intents?: Record<string, CraftIntent>): ThrustEvent[]— Advance one fixed step. Intents are keyed by craft id. Returns this step's events (also queued fordrain, up todef.eventLimit).drain(): ThrustEvent[]— Take the queued events, oldest first, and empty the queue. It holds at mostdef.eventLimitevents: if it was not drained in time the oldest were discarded.readonly droppedEvents: number— Events discarded from thedrainqueue because it was full, since the world was created.craft(id: string): CraftState— A craft by id (throws if unknown).craftDef(id: string): CraftDef— A craft's definition.load(id: string): LoadState | undefined— A load by id, if present.addCraft(def: CraftDef): CraftState— Add a craft.updateCraft(id: string, patch: Partial<Omit<CraftDef, "id">>): void— Change a craft's definition in place (a new mission's thrust, drag or steering); its state is kept.removeCraft(id: string): void— Remove a craft (its tether is released).respawn(id: string, pose: { x: number; y: number; vx?: number; vy?: number; angle?: number; fuel?: number; landed?: boolean }): void— Reset a craft to a pose: flying, gear and fuel as given (fuel default unchanged).setFuel(id: string, fuel: number): void— Set or add fuel.addLoad(def: LoadDef): LoadState— Add a load.removeLoad(id: string): void— Remove a load.addItem(def: ItemDef): ItemState— Add an item.removeItem(id: string): void— Remove an item.attach(craft: string, load: string, opts?: { length?: number; kind?: "rod" | "rope" }): void— Tether a load to a craft.release(craft: string): void— Drop a craft's load.setTerrain(chains: TerrainDef[], pads?: PadDef[]): void— Replace all terrain chains (and optionally pads).addTerrain(def: TerrainDef): void— Add a chain.removeTerrain(id: string): void— Remove a chain.setTerrainEnabled(id: string, on: boolean): void— Enable or disable a chain (doors).setGravity(g: Vec2 | number): void— Uniform gravity.setWells(wells: WellDef[]): void— Point wells.gravityAt(x: number, y: number): Vec2— Gravity acceleration at a point.upAt(x: number, y: number): Vec2— Unit vector opposite gravity at a point (straight up when there is none).tilt(id: string): number— Signed tilt of a craft's nose from local up, radians (0 upright, positive clockwise).altitude(id: string): AltitudeReading— Altitude of a craft above the surface below.raycast(from: Vec2, dir: Vec2, max: number): { point: Vec2; distance: number; chain: string; pad?: string; normal: Vec2 } | null— Cast a ray against live terrain.sweep(prev: Vec2, cur: Vec2): { point: Vec2; t: number; chain: string; pad?: string; normal: Vec2 } | null— Sweep a moving point (a bullet) against terrain.hull(id: string): Vec2[]— A craft's hull in world space.overlaps(id: string, shape: { x: number; y: number; radius: number } | Vec2[]): boolean— Does a craft's hull touch a circle or polygon?predict(id: string, seconds: number, samples?: number): Vec2[]— The ballistic path of a craft (no thrust) forseconds,samplespoints.heightAt(chain: string, x: number): number— Height of a heightfield chain at x.segments(): readonly TerrainSegment[]— Live terrain segments (for drawing).terrain(): readonly TerrainDef[]— Live terrain chain definitions.snapshot(): ThrustSnapshot— Capture everything, including the craft, load and item definitions as they are now. The queued events are not part of it.restore(s: ThrustSnapshot): void— Restore a snapshot: states, terrain, wells, gravity, pads and the definitions of its crafts, loads and items, which replace the world's own (so crafts added, changed or removed since are rolled back, and a snapshot restores into a fresh world). Empties the event queue. Throws, leaving the world untouched, if the snapshot lacks the definitions (one taken by an older version) or one for a state it holds.
ThrustWorldDef
interface ThrustWorldDef
The whole world definition.
step?: number— Fixed step in seconds. Default 1/60.bounds: BoundsDef— The world rectangle.gravity?: Vec2 | number— Uniform gravity (a number points straight down). Default 0.wells?: WellDef[]— Point wells.G?: number— Default gravitational constant for wells. Default 1.drag?: Drag— Default linear drag per second (or per axis). Default 0.terrain?: TerrainDef[]— Terrain chains.pads?: PadDef[]— Landing pads.crafts?: CraftDef[]— Crafts.loads?: LoadDef[]— Loads.items?: ItemDef[]— Fuel items.cell?: number— Broadphase cell size. Default 64.wrapMargin?: number— Width of the seam strip mirrored for wrapping worlds. Default 256.eventLimit?: number— Most events the world keeps fordrain(). Every step's events are also queued there, so a game that reads only the return value ofstep()and never drains would grow the queue forever; when it is full the oldest events are discarded (droppedEventscounts them). Whole number >= 0; 0 keeps none, for games that usestep()'s return value alone. Default 1024.
TileMask
interface TileMask
A tile grid whose solid cells are terrain. cells is row-major: a string ('#' or any non-space, non-'.' is solid) or numbers (non-zero is solid).
cols: number— Columns in the grid.rows: number— Rows in the grid.tile: number— Tile size in world units.origin?: Vec2— World position of the grid's top-left corner. Default (0, 0).cells: string | ArrayLike<number>— Row-major cells: a string of cols*rows characters or an array of numbers.
TractorDef
interface TractorDef
The tractor beam.
range: number— Reach from the craft centre.halfAngle?: number— Half-angle of the beam cone around the craft's tail (or local down for fixed crafts). Default PI (all round).attachTime?: number— Seconds a load must stay in the beam to be tethered. Omit to never tether automatically.length?: number— Tether length when the beam attaches a load. Default the distance at the moment of attachment.kind?: "rod" | "rope"— Tether kind the beam makes. Default 'rod'.drain?: number— Fuel per second drained fromdrainitems in the beam. Default 50.axis?: number— World angle of the beam's axis, radians (PI/2: straight down the screen, Thrust's fuel beam). Default the craft's tail (local down for fixed crafts).loadRange?: number— Reach for loads, when it differs from the item reach. Defaultrange.loadHalfAngle?: number— Half-angle of the cone for loads. DefaulthalfAngle.pull?: { near: number; far: number }— Tether by pulling away instead of holding (Thrust's pod): a load in the beam closer thanneararms the beam; while the beam stays on, moving the craftfaror more from the armed load tethers it atlength(defaultfar). Releasing the beam disarms it. OverridesattachTime.
Vec2
interface Vec2
A 2D point or vector (world units, y down).
x: number— Horizontal coordinate, in world units, increasing to the right.y: number— Vertical coordinate, in world units, increasing downward.
VectorThrustDef
interface VectorThrustDef
World-axis thrusters (accelerations in units/s^2).
up?: number— Acceleration upward at full deflection, in units/s^2. Default 0.down?: number— Acceleration downward at full deflection, in units/s^2. Default 0.left?: number— Acceleration to the left at full deflection, in units/s^2. Default 0.right?: number— Acceleration to the right at full deflection, in units/s^2. Default 0.
WellDef
interface WellDef extends GravitySource
A point gravity source: a star, a planet, a black hole. Negative mass repels.
id?: string— Id for edits.G?: number— Gravitational constant for this well. Default the world'sG.minR2?: number— Softening floor on r^2 so the pull stays finite at the centre. Default 100.range?: number— Beyond this distance the well does not pull. Default unlimited.


