> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reactor.inc/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> To build and serve your own model, start at /deploy/development/quickstart and /deploy/development/overview. Deploying is the default path: reactor init scaffolds a workspace, reactor auth login authenticates, and reactor model deploy registers the model, publishes the release with the weights/ folder, and activates it on Reactor's GPUs, in one command from that workspace. Docker must be running, because the publish step builds the image locally. Bump model.version in reactor.yaml before redeploying a change, because a release that already has an image is reactivated as it is. Deployment access is granted per account, so contact team@reactor.inc if a deploy is refused. Every key in reactor.yaml is documented at /deploy/platform/reactor-yaml. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. Running the model on your own machine with reactor run is optional and needs a GPU you attach with --gpus; /deploy/development/local-testing covers that loop and pairs a complete brightness model with a Python client test in a separate brightness-test workspace.
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The video model catalog — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Robotics policy documentation starts at /robotics/overview; X-WAM observations, actions, and client integration are under /robotics/xwam/; Cosmos3 Nano Policy DROID is under /robotics/cosmos/nano-policy-droid/. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# EdenLive tutorial

> Play a built-in game with EdenLive, then stream your own game into it.

EdenLive turns a simple game into a generated world, live. It accepts source video in two ways:

* **Built-in games.** EdenLive runs one of seven games for you. Your app steers it with keys and
  mouse look.
* **Your own game.** Your app renders a game in the browser and streams it as video. EdenLive
  generates the world from it, and your app keeps the controls, physics, and rules.

Part 1 plays a built-in game. Part 2 makes a game of your own and streams it in.

<Frame caption="An EdenLive example app: the generated world, with the draft it was made from in the corner.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/starter-game.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=ba9bcc8e657bc48fffa4755555d64b42" alt="An EdenLive example app playing a built-in game" width="785" height="435" data-path="images/eden-live/starter-game.webp" />
</Frame>

## Installation and setup

Install the published typed SDK in your browser app. The snippets below build on one connected
`EdenLiveModel` instance; provide your own video elements, token route, and game renderer.

The screenshots show an example app whose starter template is not yet publicly available. You can
implement the same integration with the SDK; there is no EdenLive `create-reactor-app` template to
scaffold yet.

<Tabs>
  <Tab title="npm">
    ```shell theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    npm install @reactor-models/eden-live
    ```
  </Tab>

  <Tab title="pnpm">
    ```shell theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    pnpm add @reactor-models/eden-live
    ```
  </Tab>
</Tabs>

## How EdenLive works

EdenLive reads a draft video: a built-in game, or your own. A reference image and a text prompt set
how the world looks. The model sends two video tracks back:

| Track | What it shows |
| - | - |
| `main_video` | The generated world. |
| `draft_video` | The draft frame each generated frame came from. |

After every command and every generated chunk, the model sends a `state_update` message with the
whole session state. Until you start a world, both tracks show the staged reference image.

## Authentication

Issue a session token on your server. The browser's `getToken()` helper calls that server route. Do
not put an `rk_` API key in browser code. See [Authentication](/authentication) for the token
exchange and limits.

## Part 1: Play a built-in game

### 1. Connect and show the tracks

Connect, then show `main_video` large and `draft_video` small. Give both `<video>` elements the
`muted`, `autoplay`, and `playsinline` attributes.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import { EdenLiveModel } from "@reactor-models/eden-live";

const eden = new EdenLiveModel();
const world = document.querySelector<HTMLVideoElement>("#world")!;
const draft = document.querySelector<HTMLVideoElement>("#draft")!;

eden.onMainVideo((_track, stream) => (world.srcObject = stream));
eden.onDraftVideo((_track, stream) => (draft.srcObject = stream));

await eden.connect(await getToken()); // Your server issues the token.
```

Wait for the SDK's `ready` status before sending commands.

### 2. Choose a game

EdenLive ships seven games. Each has its own goal and controls:

<CardGroup cols={3}>
  <Card title="Cryo Crossing" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-freeze.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=3545c00fdc7f30ff6f99437d49a4b0b1" width="480" height="264" data-path="images/eden-live/game-freeze.webp">
    `freeze`. Freeze a path of stones across a volcanic cavern.
  </Card>

  <Card title="Spider Hollow" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-spiders.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=2efb495d1a2f64d8060b5f26c0bd722f" width="480" height="264" data-path="images/eden-live/game-spiders.webp">
    `spiders`. Clear a torch-lit cave of spiders with an axe.
  </Card>

  <Card title="Pulse Skirmish" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-tracer.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=f71c1fa50960c6fdd8f9a9b98f9c737b" width="480" height="264" data-path="images/eden-live/game-tracer.webp">
    `tracer`. A dual-pistol fight against robots in a sunlit street.
  </Card>

  <Card title="Deepwater Shock" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-bioshock.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=43ba23c349d4bacc2e519bfe9fd6e79a" width="480" height="264" data-path="images/eden-live/game-bioshock.webp">
    `bioshock`. A wrench and electric bolts in a flooded machine room.
  </Card>

  <Card title="Kaiju Rampage" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-godzilla.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=c13fe98799632b56bcb8e738d648e2db" width="480" height="264" data-path="images/eden-live/game-godzilla.webp">
    `godzilla`. A giant monster against the towers of a miniature city.
  </Card>

  <Card title="Arcade Duel" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-arcadeduel.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=2d950e09fa55320013a3b2c0fd903629" width="480" height="264" data-path="images/eden-live/game-arcadeduel.webp">
    `arcadeduel`. Two fighters, one arena, one round.
  </Card>

  <Card title="Desert Crossfire" img="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/game-dust2.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=35afcbdf50519ea0759072d6aca3ac2c" width="480" height="264" data-path="images/eden-live/game-dust2.webp">
    `dust2`. Rifle combat through a desert courtyard.
  </Card>
</CardGroup>

`select_game` stages the game's cover as the reference image and its prompt as the scene prompt:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await eden.selectGame({ game: "freeze" });
```

The reply arrives once the game is loaded, about two seconds later. Until you start, both video
tracks show the cover.

### 3. Start the world

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await eden.start({});
```

The first frames arrive on `main_video` and `draft_video` shortly after. `start` always makes a new
world. Calling it again replaces the current one.

<Frame caption="The example app running Cryo Crossing: the game picker, the generated world with its draft, the prompt, the controls, and the game status.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/starter-running.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=1245fd194c4faede2758b03c4226da85" alt="The example app running Cryo Crossing" width="1080" height="748" data-path="images/eden-live/starter-running.webp" />
</Frame>

### 4. Steer with keys and mouse look

Send the full set of held keys each time it changes. A key tapped and released between two frames
can be missed, so hold keys long enough to see the effect.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import type { EdenLiveSetKeysParams } from "@reactor-models/eden-live";

type Key = NonNullable<EdenLiveSetKeysParams["keys"]>[number];
const ACTIONS: Key[] = ["w", "a", "s", "d", "f", " ", "shift", "e", "r", "g"];
const ARROWS: Key[] = ["arrowup", "arrowdown", "arrowleft", "arrowright"];
const KEYS = [...ACTIONS, ...ARROWS];
const held = new Set<Key>();
const toKey = (key: string) => KEYS.find((k) => k === key.toLowerCase());

let builtInControls = true;
const send = () => void eden.setKeys({ keys: [...held] }).catch(console.error);

window.addEventListener("keydown", (e) => {
  const key = toKey(e.key);
  if (!builtInControls || !key || held.has(key)) return;
  held.add(key);
  send();
});
window.addEventListener("keyup", (e) => {
  const key = toKey(e.key);
  if (key && held.delete(key)) send();
});
window.addEventListener("blur", () => {
  if (!builtInControls) return;
  held.clear();
  send();
});
```

`set_look` holds a turn and tilt rate, each from `-1` to `1`, until you replace it. Send `0, 0` when
the mouse stops. Keep one `setLook` call in flight at a time and send the latest value next.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
let stop: number | undefined;
let pending: { yaw: number; pitch: number } | undefined;
let sending: Promise<void> | undefined;

function queueLook(yaw: number, pitch: number): Promise<void> {
  pending = { yaw, pitch };
  if (!sending) {
    sending = (async () => {
      try {
        while (pending) {
          const next = pending;
          pending = undefined;
          if (!(await eden.setLook(next))) throw new Error("Mouse look refused");
        }
      } catch (error) {
        pending = undefined;
        throw error;
      } finally {
        sending = undefined;
      }
    })();
  }
  return sending;
}

function stopLook() {
  clearTimeout(stop);
  return queueLook(0, 0);
}

world.addEventListener("click", () => {
  if (builtInControls) void Promise.resolve(world.requestPointerLock()).catch(console.error);
});
document.addEventListener("mousemove", (e) => {
  if (!builtInControls || document.pointerLockElement !== world) return;
  const rate = (v: number) => Math.max(-1, Math.min(1, v / 20));
  void queueLook(rate(e.movementX), rate(-e.movementY)).catch(console.error);
  clearTimeout(stop);
  stop = window.setTimeout(() => void stopLook().catch(console.error), 100);
});
document.addEventListener("pointerlockchange", () => {
  if (builtInControls && document.pointerLockElement !== world)
    void stopLook().catch(console.error);
});
window.addEventListener("blur", () => {
  if (builtInControls) void stopLook().catch(console.error);
});
```

<Frame caption="The example app's controls light up the keys you hold. Click the world to look with the mouse.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/starter-controls.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=72a0fc89fccbf2d45601ff47e848856a" alt="Keyboard and mouse-look controls in the example app" width="464" height="123" data-path="images/eden-live/starter-controls.webp" />
</Frame>

### 5. Show the game's status

Each `state_update` carries the game's own status: its state, the player's health, what just
happened, and the result card when the game ends. Add an element with `id="status"` to your page and
provide a `showResult(outcome)` function to display the result card.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
const statusElement = document.querySelector<HTMLElement>("#status")!;

eden.onStateUpdate((state) => {
  statusElement.textContent = `${state.game_state} · health ${state.health ?? "-"} / ${state.health_max ?? "-"}`;
  if (state.ended) showResult(state.outcome); // For example ["TRY AGAIN", "You were defeated.", "…"]
});
```

`events` repeats in every `state_update` until the next chunk, so add it to a log once for each new
`chunks` value. When the game ends, the world pauses. Call `start` for a new world.

<Frame caption="Game status: the game's state and what happened in each chunk.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/starter-status.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=69502606a7e19247f158bd7068a7446a" alt="The game status panel with the latest events" width="311" height="113" data-path="images/eden-live/starter-status.webp" />
</Frame>

### 6. Change the look, pause, and restart

Change the prompt at any time. The world keeps its history, so the look changes over a few seconds:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await eden.setPrompt({ prompt: "The same volcanic cavern at night, lit by cold blue crystals." });
```

Pause and resume the same world with `setPaused`. To change the game or the reference image, pause
first, because both end the world. Then call `start` again.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await eden.setPaused({ paused: true });
await eden.selectGame({ game: "spiders" });
await eden.start({});
```

## Part 2: Stream your own game

Build any small 3D game in the browser, draw it to a canvas, and stream that canvas to EdenLive. The
game can be plain: colored boxes, simple trees, and a capsule for the player. EdenLive draws the
detail from your reference image and prompt.

<Frame caption="A draft made of simple shapes (left) and the appearance reference for it (right).">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/pair-capsule.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=ae76a72b0c36f83d2aedb771f3ae8249" alt="A capsule on a grid next to a watercolor traveler on a stone terrace" width="1934" height="528" data-path="images/eden-live/pair-capsule.webp" />
</Frame>

For help designing a playable draft, see the upstream
[game-prototype-turbo skill](https://github.com/VAST-AI-Research/EdenLive/tree/main/realtime/skills/game-prototype-turbo).
It covers game design and rendering; use the SDK integration below to stream your game to Reactor.

### 1. Draft a game that reads well

* **Keep shapes simple and distinct.** Give each object a clear silhouette and its own color. The
  model supplies the materials and lighting.
* **Make one complete round.** A clear goal, a way to win, a way to lose, and a restart.
* **Use a steady camera.** Smooth third-person or first-person movement works best.
* **Render at 1280 × 704.** EdenLive scales source frames to fill this size and crops the center. A
  canvas with the same dimensions preserves your framing.
* **Keep text out of the canvas.** Draw the score, health, and menus in your page, over the video.

<Frame caption="A walled courtyard drafted from primitives, and the watercolor look it becomes.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/pair-courtyard.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=f64048f2440f16f038e4c1730687dd85" alt="A primitive courtyard next to a watercolor courtyard" width="1936" height="528" data-path="images/eden-live/pair-courtyard.webp" />
</Frame>

### 2. Stream the canvas

Publish the canvas as `source_video` and switch the source to `client`. Keep the frame rate steady,
between 10 and 60 fps. EdenLive plays the generated video at the rate you send.

Run steps 2–4 in order in one async setup flow, and stop if a command is refused. If you are
continuing from Part 1, disable its controls and drain the last mouse-look command before switching
sources. Pause before replacing the reference; otherwise `setImage` is refused and the old reference
remains staged.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
function requireReply<T>(reply: T | undefined): T {
  if (reply === undefined) throw new Error("EdenLive command refused; see the error event");
  return reply;
}

builtInControls = false;
held.clear();
requireReply(await eden.setKeys({ keys: [] }));
await stopLook();
if (document.pointerLockElement === world) document.exitPointerLock();
requireReply(await eden.setPaused({ paused: true }));

const canvas = renderer.domElement; // Your game's 1280 × 704 canvas.
const stream = canvas.captureStream(30);
await eden.publishSourceVideo(stream.getVideoTracks()[0]);
requireReply(await eden.setSource({ source: "client" }));
```

### 3. Set the look

Capture your game's opening frame, restyle it in an image tool, and upload the result as the
reference. Keep the camera angle and object positions of the frame. Then describe the scene in the
prompt.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
const opening = await new Promise<Blob>((resolve, reject) =>
  canvas.toBlob(
    (blob) => (blob ? resolve(blob) : reject(new Error("Capture failed"))),
    "image/png",
  ),
);
// Restyle `opening` in an image tool, then upload the restyled file.
const image = await eden.uploadFile(restyledFile);
requireReply(await eden.setImage({ image }));
requireReply(
  await eden.setPrompt({
    prompt:
      "Third-person view of a traveler in an ochre coat on a pale stone terrace, in a watercolor " +
      "storybook style. Keep the scene layout and camera movement. No text or HUD.",
  }),
);
```

The [prompt guide](/model-api-reference/eden-live/prompt-guide) covers references and prompts in
depth.

### 4. Start and play

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
requireReply(await eden.start({}));
```

Your game keeps its own input, physics, and rules. EdenLive only sees the frames, so do not call
`setKeys` or `setLook` in this mode. When your game pauses, call `setPaused` too, so the world
pauses with it.

<Frame caption="Your own game in the example app: the generated world, with your game's canvas in the corner.">
  <img src="https://mintcdn.com/reactortechnologiesinc/Jm2XrDmS2Ljfk7Sz/images/eden-live/starter-own-game.webp?fit=max&auto=format&n=Jm2XrDmS2Ljfk7Sz&q=85&s=dd35ef774bcd136e2d498ed729ad8939" alt="A generated woodland camp streamed from a local game" width="785" height="429" data-path="images/eden-live/starter-own-game.webp" />
</Frame>

## Handle refusals

When EdenLive refuses a command, the call resolves `undefined` and only your client is told. The
`error` event carries the model's code, such as `pause_required`:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
eden.on("error", (error) => {
  // A refused command: error.code is the model's code, such as "pause_required".
  if (error.operation === "sendCommand") console.error(error.message);
});
```

The [schema](/model-api-reference/eden-live/schema) lists every command, field, and code. The game
thumbnails on this page are the games' opening frames from the
[EdenLive project](https://github.com/VAST-AI-Research/EdenLive) (Apache 2.0).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.