JavaScript and browser Wasm#
Node and browser clients run the same canonical Rust runtime compiled to Wasm. They receive generated JavaScript classes and ordinary objects, not raw-memory management APIs. There is no separate JavaScript simulator.
Install from npm#
npm install computerworld@0.2.0
One package serves both: Node resolves it to the CommonJS binding, which loads the Wasm beside it, and bundlers and browsers to the ES module, whose default export initializes it.
// Node (require, or import from an ES module)
const { World, engineVersion } = require('computerworld');
// Browser, through a bundler or an import map
import init, { World } from 'computerworld';
await init();
The package holds the Wasm once, the TypeScript declarations for each entry, the
reference world (computerworld/worlds/company-2026/world.json), the font pack and the
third-party notices. It is published by the release workflow from the
computerworld-0.2.0.tgz asset of the GitHub release, after checking it against that
release's SHA256SUMS.
Install a pinned bundle#
Download from v0.2.0
and verify the accompanying SHA256SUMS. Choose:
computerworld-0.2.0-wasm-web.tar.gz: browser ES module, Wasm and TypeScript declarations.computerworld-0.2.0-wasm-node.tar.gz: Node CommonJS module, Wasm, declarations and runnable Node demo.
Each archive has a top-level directory matching its filename without the archive
extension. Runtime bundles include worlds/, example code, release.json with
source/version metadata, and required notices. Keep the module, .wasm file and
notices together when copying them into an application. No Rust, wasm-bindgen or
npm installation is needed to use the downloaded bundle.
For Node:
tar -xzf computerworld-0.2.0-wasm-node.tar.gz
cd computerworld-0.2.0-wasm-node
node -e "console.log(require('./computerworld.js').engineVersion())"
# Expected: 0.2.0
node examples/javascript/computer-interaction.mjs --output ./demo-output
For a browser, unpack the web bundle and import its root module:
import init, {World, engineVersion} from './computerworld.js';
await init();
console.log(engineVersion()); // 0.2.0
// Supply your own world definition, or load a bundled worlds/ JSON file.
const world = new World(definition, 7);
Serve these files through a static HTTP server rather than opening file://. The
server serves static assets only. After bootstrap the simulated episode needs no
network backend. APIs and checkpoint formats may change between alpha releases;
do not mix wrapper/Wasm files from different versions.
Build and run from source#
git clone --branch v0.2.0 https://github.com/JacobFV/computerworld.git
cd computerworld
rustup target add wasm32-unknown-unknown
# Match Cargo.lock; currently 0.2.128.
cargo install wasm-bindgen-cli --version 0.2.128 --locked
bash scripts/build-wasm.sh
node scripts/smoke-node.cjs
node examples/javascript/computer-interaction.mjs
The build creates browser ES modules/types under pkg/web and the Node CommonJS
wrapper/types under pkg/node. Copy the corresponding directory, including its
.wasm file and notices/, into your consuming project. Node ES modules can load
the generated Node package with createRequire:
import {createRequire} from 'node:module';
import {readFileSync} from 'node:fs';
const require = createRequire(import.meta.url);
const {World} = require('./pkg/node/computerworld.js');
const definition = JSON.parse(readFileSync('./worlds/company-2026/world.json', 'utf8'));
const world = new World(definition, 7);
Paths above assume a script at the checkout root. Node initialization is synchronous. For a browser module at the checkout root:
import init, {World} from './pkg/web/computerworld.js';
await init();
const definition = await (await fetch('./worlds/company-2026/world.json')).json();
const world = new World(definition, 7);
const machine = definition.computers[0];
const env = world.environment({
actor: machine.user, machines: [machine.id],
actions: ['terminal.v1'], observations: ['terminal.v1', 'semantic.v1']
});
const result = env.step([{
family: 'terminal.v1', op: 'execute', machine: machine.id,
payload: {command: 'pwd'}
}]);
if (!result.outcomes[0].success) throw new Error(JSON.stringify(result.outcomes[0].error));
const scene = env.scene(1024, 768); // no rasterization
const frame = env.render(1024, 768);
const pixels = new Uint8ClampedArray(frame.rgba); // copy before freeing
frame.free();
const checkpoint = world.snapshot();
const branch = world.fork(checkpoint);
const branchEnv = branch.session(env.id);
That one fetch loads a static blueprint, not simulated traffic. You can bundle
or embed the definition instead. Generated initialization loads the Wasm asset;
after bootstrap, synthetic DNS/HTTP, applications, rendering and checkpoints need
no external requests. A static file server supplies correct JavaScript/Wasm MIME
types; it runs no simulation backend.
Computer input in a page#
Programmatic computer use describes keyboard,
pointer drag/resize, app/window focus, scene transforms and Canvas output. The
Node computer demo uses those
same APIs without a browser. API errors throw; failed individual actions are
reported in outcomes even when step itself returns normally.
The project site is the same module in a
page: each screen is a <canvas> that forwards coordinates and keyboard events to
Rust and paints the frame Rust returns (site/live.js, about two hundred lines). It
does not implement an alternate desktop state machine. Run it locally with
bash scripts/build-wasm.sh && cp -r pkg/web site/pkg
node scripts/serve-site.mjs 8000
Do not install a host-fetch adapter just to make synthetic domains work. A real HTML/browser compatibility bridge is optional and separate.
Ownership and lifetime#
Pass restricted Environment handles to actors. Keep World in the trusted
harness for snapshots/reset/fork, exportSnapshot()/importSnapshot(text),
stateHash(), inspection, topology edits and trajectory access. A fork reattaches
existing grants through branch.session(env.id).
Use a decimal string for a seed beyond JavaScript's safe integer range. Other
serialized integral fields outside that range use BigInt; results may contain
BigInt, so plain JSON.stringify is not a general checkpoint serializer. Use
world.exportSnapshot() for lossless portable state. Input must consist of plain
objects/arrays and supported scalar values; functions, undefined and non-finite
numbers are rejected.
Wasm objects expose free(): release temporary frames, snapshots and completed
sessions/worlds in long-running consumers. frame.rgba returns a copied Uint8Array.
Methods are synchronous; use independent Web Workers/runtime instances for
parallel worlds or to keep large renders off your UI thread. Sharing one handle
across workers is not supported.
Owner-only addComputer(computer, node, links) and removeComputer(id) change the
canonical runtime; definition() reads its blueprint. See owner lifecycle.