Reference
API
Every export of @industrieh/starmap, with its signature. Everything comes from the package root — there are no sub-paths.
Looking for explanations rather than signatures?
Building
createGalaxy
functionThe entry point. Takes your description, fills in what can be computed, checks the result, and returns a complete
Galaxy.function createGalaxy(
input: GalaxyInput,
options?: CreateGalaxyOptions,
): GalaxyParameters
- inputGalaxyInput
- At minimum
{ systems: [...] }. Not mutated. - options.validateboolean = true
- Set to
falseto skip the check. Only on a hot path whose input you have already validated.
Returns
A
Galaxy: meta, systems, lanes. Treat it as immutable.Throws
GalaxyValidationError when the finished galaxy is inconsistent — error.issues holds every problem, not just the first. TypeError if input is not an object with a systems array.const galaxy = createGalaxy({
systems: [
{ id: 'sol', name: 'Sol', x: 0, y: 0, star: { radius: 18 } },
{ id: 'vega', name: 'Vega', x: 420, y: 160, star: { radius: 22 } },
],
lanes: [{ from: 'sol', to: 'vega' }],
})
galaxy.systems[0].star.id // 'sol-star' — derived
galaxy.lanes[0].length // 449.44 — derived from the positionsFORMAT_VERSION
constantThe version of the data contract. Copied into
meta.formatVersion at build time, and bumped on every breaking change to the shape.const FORMAT_VERSION: number // currently 2if (save.meta.formatVersion !== FORMAT_VERSION) {
console.warn('This save predates the current data contract')
}Checking
validateGalaxy
functionCheck a galaxy and return its problems. Never throws, whatever you pass it — which is what makes it the right tool for data read back from disk.
function validateGalaxy(galaxy: Galaxy): GalaxyIssue[]Parameters
- galaxyGalaxy
- Anything, really. A malformed value is reported, not thrown.
Returns
An array of
{ path, message }, empty when the galaxy is sound. path locates the value, e.g. systems[3].planets[0].orbit.const galaxy = JSON.parse(json) as Galaxy
const issues = validateGalaxy(galaxy)
if (issues.length) {
console.error(issues.map((i) => `${i.path}: ${i.message}`).join('\n'))
}assertValidGalaxy
functionThe throwing form of
validateGalaxy(). This is what createGalaxy() calls internally.function assertValidGalaxy(galaxy: Galaxy): voidThrows
GalaxyValidationError if there is at least one issue.GalaxyValidationError
classThrown by
createGalaxy() and assertValidGalaxy(). The message lists every problem; issues gives you the same list structured.class GalaxyValidationError extends Error {
readonly issues: readonly GalaxyIssue[]
}try {
createGalaxy(input)
} catch (error) {
if (error instanceof GalaxyValidationError) {
for (const issue of error.issues) {
console.error(issue.path, issue.message)
}
}
}Querying
indexGalaxy
functionBuild an id → object lookup covering every kind: systems, stars, planets, stations, gates and asteroid fields.
function indexGalaxy(galaxy: Galaxy): GalaxyIndexReturns
A
ReadonlyMap of IndexEntry. Each entry carries its kind, the obj itself, and the systemId it belongs to.Build it once
Every call is a full walk. A galaxy never changes after it is built, so the index never goes stale — keep it for the session.
const index = indexGalaxy(galaxy) // walks the whole universe — do this once
const entry = index.get('sol-p1-st0')
if (entry?.kind === 'station') entry.obj.docks // typed as StationindexSystems
functionBuild an id → system lookup. Cheaper than the full index when systems are all you need.
function indexSystems(galaxy: Galaxy): SystemsByIdReturns
A
ReadonlyMap<string, StarSystem>.systemOf
functionThe system an object belongs to, from its id alone — for any kind of object. A system id returns that same system, which makes it safe to call on an id you have not inspected.
function systemOf(
source: Galaxy | GalaxyIndex,
id: string,
): StarSystem | nullParameters
- sourceGalaxy | GalaxyIndex
- Passing a
Galaxyre-indexes the universe on every call. In a loop, pass the index. - idstring
- Any object id.
Returns
The owning system, or
null if no object carries that id.systemOf(index, 'sol-p1')?.name // 'Sol' — from a planet
systemOf(index, 'sol')?.name // 'Sol' — a system returns itself
systemOf(index, 'ghost') // nulllanesOf
functionEvery hyperlane that touches a system, in either direction.
function lanesOf(galaxy: Galaxy, systemId: string): Hyperlane[]neighboursOf
functionSystems reachable in one jump. There is no guarantee there are any — your network decides.
function neighboursOf(galaxy: Galaxy, systemId: string): StarSystem[]stationsOf
functionEvery station in a system, flattened across all its planets. Takes a system, not a galaxy.
function stationsOf(system: StarSystem): Station[]findRoute
functionShortest path between two systems, weighted by
Hyperlane.length— not by geometry. Override a lane's length and you have a travel-cost model.function findRoute(
galaxy: Galaxy,
fromId: string,
toId: string,
): Route | nullReturns
{ systemIds, distance, jumps }, or null if either system does not exist or nothing links them. Routing a system to itself returns a zero-length route, not null.Complexity
Dijkstra with a linear-scan priority queue. Fine to a few thousand systems; past that, index the graph once and swap in a binary heap.
findRoute(galaxy, 'a', 'c')
// { systemIds: ['a', 'b', 'c'], distance: 200, jumps: 2 }
findRoute(galaxy, 'a', 'island') // null — the network is splitconnectedComponents
functionSplit the network into groups of systems that can reach each other.
function connectedComponents(galaxy: Galaxy): string[][]Returns
The components, largest first. A healthy galaxy returns exactly one.
const parts = connectedComponents(galaxy)
if (parts.length > 1) {
throw new Error(`Stranded systems: ${parts.slice(1).flat().join(', ')}`)
}Geometry
Conventions throughout: the star sits at (0, 0), the Y axis points down (as in SVG), and angles are degrees measured clockwise from the positive X axis.
systemPositions
functionEvery position in a system at time
t, keyed by id. Stations are placed relative to their planet, so they are carried along its orbit. Gates and asteroid fields never move.function systemPositions(system: StarSystem, t: number): Map<string, Vec2>Parameters
- systemStarSystem
- One system, not the galaxy.
- tnumber
- Simulation time, in seconds.
Returns
A Map covering the star, every planet, every station, every gate and every asteroid field.
Nothing to cache
One pass over the objects of one system — a few dozen. Call it every frame.
const at = systemPositions(system, 6)
at.get('sol-p0') // { x: -90, y: 0 }
at.get('sol-p1-st0') // the station, carried by its planetorbitPos
functionPosition on a circular orbit at a given time. The primitive underneath systemPositions — reach for it to place something the model does not know about.
function orbitPos(
orbit: number,
angleDeg: number,
period: number,
t: number,
): Vec2Parameters
- orbitnumber
- Orbit radius.
- angleDegnumber
- Starting angle, at t = 0.
- periodnumber
- Seconds per full orbit.
0means it never moves. - tnumber
- Simulation time, in seconds.
orbitPos(100, 0, 0, 0) // { x: 100, y: 0 }
orbitPos(100, 90, 0, 0) // { x: 0, y: 100 } — 90° is straight down
orbitPos(100, 0, 8, 2) // { x: 0, y: 100 } — a quarter of an 8s orbitfieldCenter
functionThe anchor point of an asteroid field: the middle of the arc for a belt, the centre for a cluster.
function fieldCenter(field: AsteroidField): Vec2toRad, round
functionTwo helpers the library uses internally and exports for the same boundary work.
round() is what keeps 0.30000000000000004 out of your save files.const toRad: (d: number) => number
const round: (n: number, d?: number) => number // d defaults to 2Round at the edges
Rounding inside a computation accumulates error instead of removing it. Do it when you serialize or hand a number to a renderer.
toRad(180) // 3.141592653589793
round(0.30000000000000004) // 0.3
round(1.23456, 3) // 1.235Randomness
createRng
functionA deterministic source of randomness from any string. Two instances made from the same seed produce the same sequence. The library never calls
Math.random().function createRng(seed: string): Rng
interface Rng {
next(): number
rand(min: number, max: number): number
randInt(min: number, max: number): number
pick<T>(arr: readonly T[]): T
chance(p: number): boolean
weighted<T extends { w: number }>(table: readonly T[]): T
}Not cryptographic
mulberry32 is a content generator. Never use it for tokens, keys, or anything an adversary benefits from predicting.
const rng = createRng('my-game-001')
rng.next() // 0.721389… float in [0, 1)
rng.randInt(1, 6) // 4 — both bounds included
rng.pick(['a', 'b', 'c']) // 'a'
rng.chance(0.5) // true
rng.weighted([{ w: 9, id: 'common' }, { w: 1, id: 'rare' }])hashSeed, mulberry32
functionThe two primitives underneath.
createRng(seed) is exactly mulberry32(hashSeed(seed)) plus the six conveniences.function hashSeed(str: string): number // FNV-1a, unsigned 32-bit
function mulberry32(a: number): () => number // the raw generatorhashSeed('sol') // 3795205537
const next = mulberry32(42)
next() // 0.601104…Types
Every type is exported from the root. The Data model covers them field by field; this is the index.
| Field | Type | Description |
|---|---|---|
GalaxyInput | input | The root of what you write. Everything else nests inside it. |
…Input | input | SystemInput, StarInput, PlanetInput, StationInput, GateInput, LaneInput, LocalRouteInput, FactionInput, AsteroidBeltInput, AsteroidClusterInput, AsteroidFieldInput. |
Galaxy | output | { meta, systems, lanes } — what createGalaxy() returns. |
StarSystem, Star, Planet, Station, Gate | output | The completed forms: every id filled in, every collection present. |
AsteroidField | output | AsteroidBelt | AsteroidCluster, discriminated by kind. |
Hyperlane, LocalRoute, ObjectRef, Faction, GalaxyMeta | output | The network, the local links, the references and the counters. |
IndexEntry | query | A discriminated union on kind. Checking it narrows obj with no cast. |
GalaxyIndex, SystemsById | query | The two ReadonlyMaps the index helpers return. |
Route | query | { systemIds, distance, jumps }. |
GalaxyIssue | validation | { path, message }. |
Vec2, Rock | geometry | { x, y }, and the [x, y, radius] tuple of an asteroid. |
ObjectKind | union | 'system' | 'star' | 'planet' | 'station' | 'gate' | 'field' — closed, unlike the ones below. |
PlanetType, StarClass, StationType, FactionId, Richness, GateStatus, RouteType | open union | Suggested values appear in autocomplete; any other string is accepted. See Open unions. |
Rng, Weighted | randomness | The generator interface, and the { w: number } constraint of a weighted table. |
CreateGalaxyOptions | options | { validate?: boolean }. |