npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@onimaxxing/worldgen-node

v0.16.0

Published

WASM entry point for ONI worldgen

Readme

@onimaxxing/worldgen

A WASM build of the Oxygen Not Included worldgen engine. Returns the same cluster the game would generate for a given coordinate.

What it covers

  • Cluster layout and starmap (world positions, POIs, rocket destinations).
  • Per-world element grid, biome polygons, and world traits.
  • Geyser, building, pickupable, and other entity spawn positions.
  • Per-cell mass, temperature, and disease (raw after worldgen.generate; settled after worldgen.advance).
  • Both basegame and Spaced Out clusters, including story traits, mixing codes, and DLC toggles.

Output is verified cell-by-cell against snapshots captured from the running game, with a slight modification to force determinism during the physics pass. The physics pass itself (temperatures, gas/liquid displacement, critter and plant placement) is exposed as worldgen.advance.

Install

# Browsers + ESM bundlers
npm install @onimaxxing/worldgen

# Node scripts, CLIs, SSR
npm install @onimaxxing/worldgen-node

Same API on both. The web package ships both a serial build and a parallel (rayon + SharedArrayBuffer) build; init() feature-detects the host and picks one. The Node package is serial-only.

Quick start

Browsers:

import init, { worldgen } from
  '@onimaxxing/worldgen';

await init();
const map = worldgen.generate('V-SNDST-C-42-0-4A-MUWF1');
// map.worlds[0].element_idx    element grid, width * height
// map.worlds[0].biome_cells    overworld biome polygons
// map.worlds[0].geysers        geyser spawn positions
// map.element_table            element names by index
// map.starmap                  hex grid world locations

Node:

// ESM
import { worldgen } from
  '@onimaxxing/worldgen-node';

const map = worldgen.generate('V-SNDST-C-42-0-4A-MUWF1');
// CommonJS
const { worldgen } =
  require('@onimaxxing/worldgen-node');

const map = worldgen.generate('V-SNDST-C-42-0-4A-MUWF1');

init() is a no-op on Node (WASM is loaded synchronously at import time), so the same code runs on both targets.

Types ship in the package .d.ts:

import type { MapData, SettleSnapshot } from
  '@onimaxxing/worldgen';

Parallel vs serial (web only)

The web package ships two complete wasm-bindgen builds under serial/ and parallel/. init() picks one at runtime:

  • Parallel: loaded when globalThis.crossOriginIsolated === true and SharedArrayBuffer is available. Runs the settle sim across a rayon worker pool (navigator.hardwareConcurrency workers by default; override with init({ threads: N })). Requires COOP/COEP response headers on the page.
  • Serial: loaded otherwise. No worker pool, no shared memory, no headers required.

Loading the WASM binary

await init() with no arguments resolves the .wasm via the bundler-rewritten new URL('oni_wasm_bg.wasm', import.meta.url) pattern inside the selected build. For hosts that don't rewrite that pattern, pass the URL explicitly:

// Served from your own origin
await init({ module_or_path: '/wasm/oni_wasm_bg.wasm' });

// Pre-fetched bytes or a compiled WebAssembly.Module
await init({ module_or_path: await fetch(url) });
await init({ module_or_path: myWebAssemblyModule });

The URL is interpreted by the selected build; to pin a specific .wasm, also pass threads: 'serial'.

Coordinates

Standard game coords. Examples:

  • SNDST-A-42-0-0-0 basegame Sandstone, seed 42, no stories or mixing
  • V-SNDST-C-42-0-4A-MUWF1 Spaced Out Vanilla Sandstone, all stories and mixing
  • CER-C-100-0-4A-MUWF1 Spaced Out Ceres, seed 100

API

The public surface is a default init export and a worldgen singleton wrapping the one-slot cluster cache. worldgen.generate(coord) populates the cache; every other method operates on whatever's currently in it.

Generation

worldgen.generate(coord: string): MapData

Generate a cluster from a game coordinate and return the decoded map (see MapData shape). Caches the cluster internally, replacing anything previously cached.

worldgen.entities(): EntitySpawners

Re-run ambient mob spawning against the cached cluster's current cell state and return the refreshed entity lists. Call after worldgen.advance chunks when settled liquid/gas flow may have invalidated earlier mob placements. Requires a prior worldgen.generate. Template-placed entities (geysers, oil wells, props) persist across calls.

worldgen.version(): string

The ONI game build this WASM was baselined against, e.g. "737195".

Settle simulation

Worldgen takes hundreds of milliseconds; settling takes several seconds. The API is split so you can show a preview first and fill in settled data progressively.

worldgen.advance(targetTick: number): SettleSnapshot

Advance the cached cluster's settle sim to targetTick (in 1..=499) and return a decoded snapshot at that tick. Call repeatedly with increasing ticks to paint intermediate frames. The settle runs 499 ticks total; the last call with targetTick = 499 finalises the cluster.

const preview = worldgen.generate(coord);
renderPreview(preview);

for (let tick = 50; tick < 499; tick += 50) {
  const snapshot = worldgen.advance(tick);
  renderFrame(snapshot);
}
const final = worldgen.advance(499);
renderFrame(final);

worldgen.clear();

Throws if the cache is empty or targetTick is out of range.

Editor

The active settings live in a single slot exposed as a { path → JSON-string } files map. The path keys are Klei's config paths (e.g. expansion1::worldgen/clusters/CGMCluster.yaml); each value is the JSON-encoded file body.

const files = worldgen.snapshot.files();
const cluster = JSON.parse(files['expansion1::worldgen/clusters/CGMCluster.yaml']);
//...mutate cluster...
files['expansion1::worldgen/clusters/CGMCluster.yaml'] = JSON.stringify(cluster);
worldgen.snapshot.load(files);
// next worldgen.generate(...) uses the mutated settings

| Method | Description | |---|---| | worldgen.snapshot.loadVanilla() | Reset SETTINGS to the embedded baseline. Strict mode. | | worldgen.snapshot.loadVanillaExtensions() | Same, Extensions mode. Accepts mod-defined enum extensions on subsequent merges. | | worldgen.snapshot.load(files) | Replace SETTINGS with files. Strict; atomic. Throws and leaves SETTINGS untouched on invalid input. | | worldgen.snapshot.loadExtensions(files) | Extensions-mode counterpart of load. | | worldgen.snapshot.merge(files) | Last-wins merge into the current SETTINGS. Auto-loads vanilla if empty. The typical mod-overlay entry point. | | worldgen.snapshot.remove(paths) | Drop paths from the current SETTINGS. | | worldgen.snapshot.files() | Fresh deep-copy of the current SETTINGS. load(files()) is a no-op round-trip. | | worldgen.snapshot.checksum() | Stable hex hash of the current snapshot. Same content → same hash regardless of load history. | | worldgen.snapshot.schemaVersion() | The ONI game build (e.g. "737195"). Persist alongside snapshot bytes for migration checks. |

For ingesting mod YAML, the package also exports parseKleiYaml:

import { parseKleiYaml } from '@onimaxxing/worldgen';
import { readFileSync } from 'node:fs';

const json = parseKleiYaml(readFileSync('mod/cluster.yaml', 'utf8'));
worldgen.snapshot.merge({
  'expansion1::worldgen/clusters/MyMod.yaml': JSON.stringify(json),
});

Any load* / merge / remove invalidates the cluster cache.

Cache lifecycle

worldgen.clear(): void
worldgen.reset(): void
  • clear() drops the cached generated cluster; snapshot edits persist.
  • reset() drops both the cluster cache and the snapshot; next generate() reloads the embedded stock settings from scratch.

Cache semantics:

  • One slot for the cluster cache. New generate() replaces it.
  • Per-worker. Both caches are thread-local in WASM, so each Web Worker (or Node worker thread) has its own independent state.
  • Snapshot edits evict the cluster cache. Mutated settings make the cached cluster stale.

Running in a web worker

The WASM module is several megabytes and worldgen can block the thread for hundreds of milliseconds; settling several seconds. Run it in a worker to keep the main thread responsive.

// worker.ts
import init, { worldgen } from
  '@onimaxxing/worldgen';

await init();

self.onmessage = async ({ data: { coord } }) => {
  const preview = worldgen.generate(coord);
  self.postMessage({ kind: 'preview', data: preview });

  for (let tick = 50; tick < 499; tick += 50) {
    const snapshot = worldgen.advance(tick);
    self.postMessage({ kind: 'frame', data: snapshot });
  }
  self.postMessage({ kind: 'frame', data: worldgen.advance(499) });

  worldgen.clear();
};
// main thread
const worker = new Worker(new URL('./worker.ts', import.meta.url),
                          { type: 'module' });
worker.onmessage = ({ data: { kind, data } }) => {
  if (kind === 'preview') renderPreview(data);
  if (kind === 'frame')   renderFrame(data);
};
worker.postMessage({ coord: 'V-SNDST-C-42-0-4A-MUWF1' });

MapData shape

interface MapData {
  coordinate: string;
  seed: number;
  cluster_id: string;
  coordinate_prefix: string;          // short tag e.g. "SNDST-C", "V-BAD-C", "VOLCA"
  element_table: string[];            // element names indexed by element_idx
  starmap: StarmapEntry[];            // Spaced Out hex grid world locations
  starmap_pois: StarmapPoi[];         // Spaced Out non-asteroid hex POIs
  vanilla_starmap: VanillaStarmapEntry[]; // basegame rocket destinations
  worlds: WorldMapData[];
  failure: WorldgenFailure | null;    // populated on fatal worldgen error
  telemetry: WorldgenEvent[];         // fail-slow warnings (empty on clean runs)
}

interface WorldgenFailure {
  stage: string;                      // pipeline stage that reported the error
  world_index: number;                // -1 for cluster-level failures
  message: string;
}

interface WorldgenEvent {
  category: string;                   // e.g. "layout", "mob_spawning", "template_rules"
  message: string;                    // per-world entries prefixed with "world[N]:"
}

interface WorldMapData {
  name: string;                       // world config path
  width: number;
  height: number;
  is_starting: boolean;
  world_traits: string[];
  element_idx: Uint16Array;           // u16 per cell, row-major (width * height)
  mass: Float32Array;                 // f32 per cell
  temperature: Float32Array;          // f32 per cell
  disease_idx: Uint8Array;            // u8 per cell, 255 = none
  disease_count: Int32Array;          // i32 per cell
  backwall_element_idx: Uint16Array;  // U59 backwall layer; Vacuum-filled outside DLC5 aquatic biomes
  backwall_mass: Float32Array;        // f32 per cell, kg; 0 on Vacuum cells
  backwall_temperature: Float32Array; // f32 per cell, K; 0 on Vacuum cells
  zone_idx: Uint16Array;              // u16 per cell -> zone_table; 0xFFFF = no biome polygon
  zone_table: string[];               // distinct ZoneType strings (same vocab as biome_cells[].zone_type)
  subworld_idx: Uint16Array;          // u16 per cell -> subworld_table; 0xFFFF = no biome polygon
  subworld_table: string[];           // distinct subworld type paths (same vocab as biome_cells[].type)
  biome_cells: BiomeCell[];
  geysers: GeyserSpawn[];
  buildings: EntitySpawn[];
  pickupables: EntitySpawn[];
  other_entities: EntitySpawn[];
}

interface BiomeCell {
  id: number;
  type: string;                       // subworld type path
  zone_type: ZoneType | null;         // see ZoneType union exported from the package
  x: number;
  y: number;
  poly: number[];                     // flat [x0,y0,x1,y1,...]
  tags: string[];                     // layout tags ("AtSurface", "AtDepths", distance/zone tags, subworld tags)
}

interface EntitySpawn {
  tag: string;                        // game prefab name
  cell: number;                       // grid cell index
  x: number;                          // cell % width
  y: number;                          // cell / width
  connections?: number;               // TemplatePrefab.connections bitmask (conduit-aware buildings only)
  rotationOrientation?: string;       // TemplatePrefab.rotationOrientation, e.g. "R90", "R270", "FlipH"
}

interface GeyserSpawn extends EntitySpawn {
  type: string;                       // resolved geyser template name
  // Present when geyser stats were rolled:
  scaled_rate?: number;
  scaled_iter_len?: number;
  scaled_iter_pct?: number;
  scaled_year_len?: number;
  scaled_year_pct?: number;
}

interface StarmapEntry {
  world_index: number;
  q: number; r: number;               // hex grid coords
}

interface StarmapPoi {
  poi_type: string;                   // e.g. "HarvestableSpacePOI_*", "ArtifactSpacePOI"
  q: number; r: number;
  // Only on harvestable POIs:
  capacity_roll?: number;
  recharge_roll?: number;
  total_capacity?: number;
  recharge_time?: number;
}

// Basegame rocket destinations. Empty on Spaced Out clusters.
interface VanillaStarmapEntry {
  type: string;                       // destination type id
  distance: number;                   // distance tier
}

SettleSnapshot shape

worldgen.advance returns typed-array views over cell data, not a JSON array. One snapshot covers every world in the cluster.

interface SettleSnapshot {
  tick: number;                       // 1..=499
  worlds: SettleWorld[];
}

interface SettleWorld {
  width: number;
  height: number;
  element_idx: Uint16Array;
  mass: Float32Array;
  temperature: Float32Array;
  disease_idx: Uint8Array;            // 255 = none
  disease_count: Int32Array;
  backwall_element_idx: Uint16Array;  // U59 backwall layer; Vacuum-filled outside DLC5 aquatic biomes
  backwall_mass: Float32Array;        // f32 per cell, kg; 0 on Vacuum cells
  backwall_temperature: Float32Array; // f32 per cell, K; 0 on Vacuum cells
}

Performance

Worldgen runtime, same machine both rows. Coordinate: V-SNDST-C-42-0-4A-MUWF1, the Spaced Out Vanilla Sandstone cluster at seed 42, with every story trait (4A) and every DLC mixing option (MUWF1) enabled. 8 worlds, ~170,000 cells total.

Time per seed:

| Runtime | Time | |---|---| | In-game World Generation | 10.4 s | | This package (Node 24) | 0.48 s |

WASM worldgen runs roughly 22x faster than the game's C# worldgen.

Memory usage (one worldgen at a time, measured on the same cluster, resident memory only):

| Runtime | Working set | |---|---| | In-game World Generation | ~3.3 GB | | This package (Node 24) | ~0.2 GB |

WASM worldgen uses roughly 15x less memory than the game's C# worldgen.

Package size is dominated by the two WASM modules; only one loads at runtime. Load it in a web worker to keep download and instantiate off the main thread.

License

MIT.