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

# HappyOyster overview

> Build and control real-time HappyOyster worlds with the typed JavaScript and React SDK.

export const ModelRate = ({model}) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(false);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const ctrl = new AbortController();
    fetch("https://api.reactor.inc/pricing", {
      signal: ctrl.signal
    }).then(r => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    }).then(json => {
      setData(json);
      setLoading(false);
    }).catch(err => {
      if (err.name === "AbortError") return;
      setError(true);
      setLoading(false);
    });
    return () => ctrl.abort();
  }, []);
  if (loading) {
    return <span aria-label="loading pricing" className="inline-block h-4 w-28 rounded bg-zinc-950/10 dark:bg-white/10 animate-pulse align-middle" />;
  }
  const creditsPerDollar = data?.settings?.credits_per_dollar;
  const amountPerSec = data?.models?.find(m => m.name === model)?.rate?.amount_per_sec;
  const canRender = !error && creditsPerDollar && typeof amountPerSec === "number";
  if (!canRender) {
    return <span className="text-zinc-950/60 dark:text-white/60">see current rate below</span>;
  }
  const perHour = Math.round(amountPerSec / creditsPerDollar * 3600);
  const perSec = (amountPerSec / creditsPerDollar).toFixed(4);
  return <span>
      <strong>${perHour}/hr</strong> (${perSec}/sec)
    </span>;
};

HappyOyster is a real-time interactive world model with two experiences:

* **Adventure** — explore a world with held movement, look, and interaction controls.
* **Directing** — steer a story with text instructions, pause, resume, and rewind.

The experience is selected when you construct the client or mount the React provider. It stays fixed
for that session because each experience connects to its own Reactor model.

Build on HappyOyster with the typed
[`@reactor-models/happy-oyster`](https://www.npmjs.com/package/@reactor-models/happy-oyster) SDK. It
provides the `HappyOysterModel` class, React bindings, world lifecycle methods, live controls, and
the `<HappyOysterVideo>` element.

## At a glance

| Spec             | Value                                                      |
| ---------------- | ---------------------------------------------------------- |
| SDK package      | `@reactor-models/happy-oyster`                             |
| SDK modes        | `"adventure"` and `"directing"`                            |
| Pricing          | <ModelRate model="happy-oyster" />                         |
| Resolution       | Directing: 480p or 720p; Adventure: 720p                   |
| World input      | Text prompt and optional starting image                    |
| Live interaction | Adventure controls or Directing instructions and transport |

## Install

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

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

## Choose an experience

Use `mode: "adventure"` for movement controls or `mode: "directing"` for text steering. Set it
before connecting:

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { HappyOysterModel } from "@reactor-models/happy-oyster";

  const model = new HappyOysterModel({
    mode: "directing",
    videoElement,
  });
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { HappyOysterProvider, HappyOysterVideo } from "@reactor-models/happy-oyster/react";

  export function App({ jwt }: { jwt: string }) {
    return (
      <HappyOysterProvider mode="directing" jwt={jwt} autoConnect>
        <HappyOysterVideo className="aspect-video w-full" />
        <Controller />
      </HappyOysterProvider>
    );
  }
  ```
</CodeGroup>

To switch experiences, disconnect and construct a new client. In React, remount the provider with a
different `key`.

## World lifecycle

A session has one current world. Worlds persist after a session ends, so save the
`encrypted_world_id` returned by `createWorld()` and use `attachWorld()` to return later.

```text theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
connect → createWorld / attachWorld → startTravel → controls → endTravelSession / disconnect
```

1. `connect()` opens the Reactor session and synchronizes the first `worldState` snapshot.
2. `createWorld(params)` builds a new world for the client's selected mode.
3. `attachWorld(encryptedWorldId)` reopens an existing world of the same mode.
4. `startTravel()` begins the live world stream.
5. Adventure uses `move`, `look`, `interact`, `release`, and `stop`.
6. Directing uses `instruct`, `pause`, `resume`, and `rewind`.

The model publishes authoritative `worldState` and `travelState` snapshots. Drive UI from those
snapshots instead of reconstructing state from individual calls.

## Quick start

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { HappyOysterModel } from "@reactor-models/happy-oyster";

  const model = new HappyOysterModel({
    mode: "adventure",
    videoElement: document.querySelector("video")!,
  });

  model.onWorldState((state) => {
    console.log(state.phase, state.encrypted_world_id);
  });

  await model.connect(jwt);

  const world = await model.createWorld({
    prompt: "A sunny grassy meadow with rolling green hills, wildflowers, and a winding dirt path.",
    perspective: "third_person",
  });

  saveWorldId(world.encrypted_world_id);
  await model.startTravel();
  await model.move("Front");
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  "use client";

  import {
    HappyOysterProvider,
    HappyOysterVideo,
    useHappyOyster,
  } from "@reactor-models/happy-oyster/react";

  function Controller() {
    const { createWorld, startTravel, worldState } = useHappyOyster();

    async function enter() {
      await createWorld({
        prompt: "A rain-slick neon alley at midnight.",
        resolution: "720p",
        narrative: "Dramatic",
      });
      await startTravel();
    }

    return (
      <button onClick={enter}>{worldState?.phase === "ready" ? "Enter world" : "Build world"}</button>
    );
  }

  export function App({ jwt }: { jwt: string }) {
    return (
      <HappyOysterProvider mode="directing" jwt={jwt} autoConnect>
        <HappyOysterVideo className="aspect-video w-full" />
        <Controller />
      </HappyOysterProvider>
    );
  }
  ```
</CodeGroup>

See the [tutorial](/model-api-reference/happy-oyster/tutorial) for a full application flow, the
[schema reference](/model-api-reference/happy-oyster/schema) for every typed method and state field,
and the [prompt guide](/model-api-reference/happy-oyster/prompt-guide) for world authoring.
