---
name: rtie-realtime-sync
description: Build realtime multiplayer experiences on RTIE — SpacetimeDB via @rtie/sdk and module manifests, never direct SDK calls.
version: "0.1.0"
---

# RTIE Realtime and Sync

RTIE's realtime layer is backed by **SpacetimeDB** (CF stack) or **Electric SQL** (self-hosted stack).
You never call either directly — all access goes through `@rtie/sdk` or the RTIE module manifest.

## The rule

```typescript
// ✅ Correct — via @rtie/sdk
import { createClient } from "@rtie/sdk";
const rtie = createClient({ apiKey: process.env.RTIE_API_KEY! });

// ❌ Wrong — never import SpacetimeDB or Electric SQL directly
import { DbConnection } from "@clockworklabs/spacetimedb-sdk";
import { ElectricClient } from "electric-sql";
```

RTIE provisions an isolated SpacetimeDB module per tenant. The `@rtie/sdk` client knows which module instance to connect to based on your API key.

## Presence — who is online

```typescript
// Get current presence snapshot
const presence = await rtie.presence.get(roomId);
// → [{ userId, displayName, joinedAt, metadata }, ...]

// Watch for changes (returns unsubscribe function)
const unsub = rtie.presence.watch(roomId, (snapshot) => {
  console.log("Participants:", snapshot.length);
});
// cleanup
unsub();
```

MCP equivalent: `get_presence(roomId)` — useful for agents querying room state.

## Chat — realtime messaging

```typescript
// Send a message
await rtie.chat.send(roomId, { body: "Hello!" });

// List recent messages
const messages = await rtie.chat.list(roomId, { limit: 50 });

// Subscribe to new messages
const unsub = rtie.chat.subscribe(roomId, (msg) => {
  console.log(msg.authorId, ":", msg.body);
});
```

## Game sync (vArcade / multiplayer)

Game state sync uses the `sync` module, which provisions SpacetimeDB tables and reducers per game per tenant.

```typescript
// List games your tenant has registered
const games = await rtie.sync.listGames();

// Create a new game definition
const game = await rtie.sync.createGame({ name: "Tower Defense" });

// Create a room for a game session
const room = await rtie.sync.createRoom({ gameId: game.id });

// Push authoritative state (server → all players)
await rtie.sync.pushState(room.id, {
  wave: 3,
  lives: 10,
  score: 4200,
});

// Get current state snapshot
const state = await rtie.sync.getState(room.id);

// Broadcast event to all players
await rtie.sync.broadcastEvent(room.id, {
  type: "enemy_wave_start",
  payload: { count: 12, type: "goblin" },
});
```

MCP tools: `sync_list_games`, `sync_create_game`, `sync_create_room`, `sync_push_state`, `sync_broadcast_event`

## Declaring SpacetimeDB tables in a module manifest

For custom module state that needs to be synced in real-time, declare it in the manifest:

```typescript
// modules/my-module/manifest.ts
export const manifest: ModuleManifest = {
  id: "my-module",
  // ... rest of manifest
  spacetime: {
    tables: [
      {
        name: "my_game_objects",
        columns: [
          { name: "id", type: "u64", primaryKey: true },
          { name: "position_x", type: "f32" },
          { name: "position_y", type: "f32" },
          { name: "owner_id", type: "Identity" },
        ],
      },
    ],
    reducers: [
      {
        name: "move_object",
        args: [
          { name: "object_id", type: "u64" },
          { name: "x", type: "f32" },
          { name: "y", type: "f32" },
        ],
      },
    ],
  },
};
```

The RTIE provisioner composes these tables into the tenant's SpacetimeDB module at enablement time.
You do not write the SpacetimeDB Rust/WASM module yourself — declare the schema, RTIE generates it.

## React client integration

```typescript
import { createClient } from "@rtie/sdk";
import { useState, useEffect } from "react";

const rtie = createClient({ apiKey: import.meta.env.VITE_RTIE_API_KEY });

function PresenceList({ roomId }: { roomId: string }) {
  const [participants, setParticipants] = useState([]);

  useEffect(() => {
    const unsub = rtie.presence.watch(roomId, setParticipants);
    return unsub;
  }, [roomId]);

  return <ul>{participants.map(p => <li key={p.userId}>{p.displayName}</li>)}</ul>;
}
```

For TanStack DB integration (Electric SQL stack — self-hosted), use `@rtie/sdk/electric` — same API surface, different transport.

## Provider abstraction

The realtime provider is configured at the tenant level. You never choose SpacetimeDB or Electric SQL in application code:

```typescript
// ✅ SDK handles provider selection transparently
rtie.presence.watch(roomId, callback);

// ❌ Wrong — hardcodes CF stack
import { SpacetimePresence } from "@clockworklabs/spacetimedb-sdk";
```

Full docs: https://docs.rtie.ai/modules/sync
