Install the client
The client is the runtime: it fetches a game’s code, runs it and traces it on the device’s own GPU. There is one for macOS (macOS 14 or later, Apple silicon), one for iPhone and iPad (iOS 17 or later), one for the Steam Deck and Linux (x86_64) and one for Android (arm64, Android 10 or later); the last two play what is on threert.app. The macOS one also carries the threert command line you develop with (step 4).
Open an example
ThreeRT for macOS opens on its library: the dev servers running on your machine, the games you opened recently and a set of examples. threert.app hosts the same examples, next to what others published: on an app’s page, Open in ThreeRT hands its link to the client.
A client opens three kinds of link: threert://host:port/path (plain HTTP, what a dev server on your network uses), threerts://host/path (HTTPS) and an ordinary https:// URL of a package.
In a game: drag to orbit, right-drag and WASD to fly, scroll or pinch to zoom, click to play. On a phone, one finger orbits, two pan and a tap plays.
Write a scene
A scene is a module whose default export builds a Scene and a camera with @threert/scene, a three.js-style API: meshes, geometries, MeshStandardMaterial, lights, glTF models. Leave the settings alone. The sky, the shadows, the bounce light and the reflections come from tracing the scene, not from options.
Loading material-grid.ts…
This is the first slide of the hello world on the front page, where it runs next to the same code in three.js. Light is in physical units: the sun above is 80,000 lux, and an emissive surface is a light source.
From a scene to a game
@threert/gameplay adds state and input. defineGame takes the initial state, a setup that builds the scene, handlers that change the state, and a frame script that shows the state in the scene every frame.
import { Mesh, MeshStandardMaterial } from "@threert/scene";
import { defineGame } from "@threert/gameplay";
export default defineGame({
state: { lit: {}, night: false },
setup() {
// build the scene as above; give the objects you will change an id
// orb.id = "orb_0";
return { scene, camera };
},
// Handlers change the state, and only the state.
handlers: {
click(tx, ev) {
tx.decide(ev.target.startsWith("orb_"), `nothing happens when you click ${ev.target}`);
tx.state.lit[ev.target] = !tx.state.lit[ev.target];
},
night(tx) {
tx.state.night = !tx.state.night;
},
},
keys: { L: "night" },
// The frame script derives the scene from the state, every frame.
frame(f) {
const orb = f.object("orb_0");
orb.material.emissiveIntensity = f.state.lit.orb_0 ? 2500 : 0;
orb.position.y = 1 + 0.08 * Math.sin(f.time * 2);
},
});
A click reaches the game as a click event with the object under the cursor as its target; keys maps a key to a handler. Handlers are transactions: tx.decide refuses one, and nothing is half-written. What needs the engine’s own world (physics, a ray cast into the scene) goes in world handlers, described in RUNTIME.md.
The dev loop
The threert command line creates a project, serves it to every client on your network and reloads them on each save. It ships inside ThreeRT for macOS, with everything it needs: open the app and press Install threert under Develop in its library. That links the command into /usr/local/bin (macOS asks for your password), or into ~/.local/bin when that is already on your PATH.
$ threert create my-game $ cd my-game $ threert dev # bundles on save, serves the package, prints a QR code
- On the Mac, ThreeRT for macOS opens the game as the server starts.
- On an iPhone, scan the QR code with the ThreeRT app (or the camera): it carries
threert://and your machine’s address. - In any browser, the server’s address shows the same game as three.js.
Save the file and every client reloads. A build error shows over the last good build, and the game’s console.* lines print in the terminal that runs the server.
| Command | What it does |
|---|---|
threert create <dir> | A new game from the template: an interactive scene, a tsconfig, and AGENTS.md / CLAUDE.md so a coding agent can drive the same loop |
threert dev | The dev server: the package, the three.js page, live reload, the clients’ logs |
threert check | Bundles, typechecks and runs setup and three frames without a window: objects, lights, files read and missing, handlers, keys |
threert shot | The traced frame as a PNG, once it has settled |
threert click · key · event · state | Play the open game from the terminal and read its state |
threert pack | A static package of the game for any web host (below) |
The command is a link into the app, so updating the app updates it. If you move the app, press Install threert again; threert dev and threert check repair a project’s tsconfig.json when it still names the old place.
Packages
A game is distributed as code. A package is a directory on any static host, with a manifest, threert.json, at its base URL. threert pack writes one; the dev server serves one that changes as you save.
$ threert pack # -> dist/: threert.json, game.js, game.mjs, index.html, assets/ # upload dist/ to any static host, then, on any device with the client: # threerts://your.host/path
| threert.json | |
|---|---|
format | "threert/1" |
id · name · version | What the game is called; id is lowercase letters, digits and dashes |
hash | The hash of the code: it changes on every build, and a client caches the code by it |
entry | The game as one script, with the runtime it was built against (game.js) |
module | Optional: the game as an ES module that imports @threert/scene and @threert/gameplay from the client (game.mjs) |
assets | The files the game may read, each with its size and SHA-256. A client fetches the ones the scene uses, on demand, and checks them |
web | The same game as a three.js page (index.html), for screens the client cannot trace on |
minClient | The oldest client version that runs this package |
Game code runs in the engine’s embedded JavaScript runtime with no file system, network or timers of its own, and the files it reads stay inside the package.
Publish to threert.app
threert.app hosts packages for you. One command gives a project its own address, a page in the catalogue and, for a game that uses @threert/net, multiplayer rooms. It needs a GitHub account.
$ threert login # once: it prints a code, you approve it with your GitHub account $ threert publish # packs the project, takes a cover, uploads what changed, prints the address
The first publish claims the app’s name, first come, first served, and the name is its address: your-app.threert.app. Publishing again adds a version. In ThreeRT for macOS, Studio’s Publish does the same. Names, covers and limits are on threert.app/publish.
Reference
The engine is open source, and its documents live with the code.