Docs · Getting started

Write three.js-style code.
Get it traced.

A ThreeRT game is a TypeScript file. The client runs it and path-traces the scene it builds; the same file runs as three.js in a browser. This page takes you from the client to a game on your phone.

01

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).

02

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.

03

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.

04

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.

CommandWhat 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 devThe dev server: the package, the three.js page, live reload, the clients’ logs
threert checkBundles, typechecks and runs setup and three frames without a window: objects, lights, files read and missing, handlers, keys
threert shotThe traced frame as a PNG, once it has settled
threert click · key · event · statePlay the open game from the terminal and read its state
threert packA 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.

05

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 · versionWhat the game is called; id is lowercase letters, digits and dashes
hashThe hash of the code: it changes on every build, and a client caches the code by it
entryThe game as one script, with the runtime it was built against (game.js)
moduleOptional: the game as an ES module that imports @threert/scene and @threert/gameplay from the client (game.mjs)
assetsThe 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
webThe same game as a three.js page (index.html), for screens the client cannot trace on
minClientThe 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.

06

Reference

The engine is open source, and its documents live with the code.