<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# Multi-Region

Demonstrates deploying Rivet Actors across multiple geographic regions for low-latency global access.

## Getting Started

```sh
git clone https://github.com/rivet-dev/rivet.git
cd rivet/examples/multi-region
npm install
npm run dev
```


## Features

- **Multi-region deployment**: Deploy actors across multiple geographic regions automatically
- **Low-latency access**: Users connect to the nearest region for optimal performance
- **Automatic routing**: Requests automatically routed to the appropriate regional deployment
- **Global state management**: Actor state synchronized across regions as needed

## Implementation

This example demonstrates deploying actors across multiple regions:

- **Actor Definition** ([`src/backend/registry.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/multi-region/src/backend/registry.ts)): Shows configuration for multi-region actor deployment

## Resources

Read more about [actions](/docs/actors/actions), [state](/docs/actors/state), and [setup](/docs/setup).

## License

MIT
">
<input type="hidden" name="project[files][index.html]" value="&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Quickstart: Multi-Region&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f0f0f0;
        }
        .app-container {
            max-width: 800px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .header h1 {
            color: #333;
            margin: 0;
        }
        .header p {
            color: #666;
            margin: 10px 0;
        }
        .info-box {
            background-color: #e8f4f8;
            border: 1px solid #b8d4da;
            border-radius: 6px;
            padding: 15px;
            margin-bottom: 20px;
        }
        .info-box h3 {
            margin: 0 0 10px 0;
            color: #2c5aa0;
        }
        .info-box p {
            margin: 0;
            color: #555;
            line-height: 1.5;
        }
        .region-selector {
            margin-bottom: 20px;
            text-align: center;
        }
        .region-selector label {
            margin-right: 10px;
            color: #333;
        }
        .region-selector select {
            padding: 8px 12px;
            font-size: 14px;
            border: 2px solid #4287f5;
            border-radius: 4px;
            background-color: white;
            cursor: pointer;
        }
        .region-info {
            display: flex;
            justify-content: space-around;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        .info-card {
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 6px;
            padding: 10px 15px;
            text-align: center;
            flex: 1;
            min-width: 150px;
        }
        .info-card strong {
            color: #333;
            display: block;
            margin-bottom: 5px;
        }
        .game-area {
            text-align: center;
            margin-bottom: 20px;
        }
        .game-canvas {
            border: 3px solid #333;
            border-radius: 8px;
            background-color: #fafafa;
            max-width: 100%;
            height: auto;
        }
        .controls {
            margin-top: 20px;
            text-align: center;
        }
        .controls p {
            margin: 5px 0;
            font-size: 14px;
            color: #555;
        }
        .connection-status-wrapper {
            position: relative;
            height: 0;
        }
        .connection-status {
            position: absolute;
            top: -10px;
            right: -10px;
            padding: 8px 12px;
            border-radius: 4px;
            font-size: 14px;
            font-weight: bold;
        }
        .connection-status.connected {
            background-color: #d4edda;
            color: #155724;
        }
        .connection-status.disconnected {
            background-color: #f8d7da;
            color: #721c24;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/frontend/main.tsx&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;multi-region&quot;,&quot;version&quot;:&quot;2.0.21&quot;,&quot;private&quot;:true,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite&quot;,&quot;check-types&quot;:&quot;tsc --noEmit&quot;,&quot;test&quot;:&quot;vitest run&quot;,&quot;build&quot;:&quot;vite build &amp;&amp; vite build --mode server&quot;,&quot;start&quot;:&quot;srvx --static=public/ dist/server.js&quot;},&quot;devDependencies&quot;:{&quot;@types/node&quot;:&quot;^22.13.9&quot;,&quot;@types/react&quot;:&quot;^18.2.0&quot;,&quot;@types/react-dom&quot;:&quot;^18.2.0&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.2.0&quot;,&quot;tsx&quot;:&quot;^3.12.7&quot;,&quot;typescript&quot;:&quot;^5.5.2&quot;,&quot;vite&quot;:&quot;^5.0.0&quot;,&quot;vitest&quot;:&quot;^3.1.1&quot;,&quot;vite-plugin-srvx&quot;:&quot;^1.0.0&quot;},&quot;dependencies&quot;:{&quot;@hono/node-server&quot;:&quot;^1.19.7&quot;,&quot;@hono/node-ws&quot;:&quot;^1.3.0&quot;,&quot;@rivetkit/react&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@b7a6ac3488d8d64f2cddf185312f05cf519f9413&quot;,&quot;hono&quot;:&quot;^4.7.4&quot;,&quot;react&quot;:&quot;^18.2.0&quot;,&quot;react-dom&quot;:&quot;^18.2.0&quot;,&quot;rivetkit&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/rivetkit@b7a6ac3488d8d64f2cddf185312f05cf519f9413&quot;,&quot;srvx&quot;:&quot;^0.10.0&quot;},&quot;template&quot;:{&quot;technologies&quot;:[&quot;typescript&quot;],&quot;tags&quot;:[],&quot;priority&quot;:1000,&quot;frontendPort&quot;:5173},&quot;license&quot;:&quot;MIT&quot;}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
	&quot;compilerOptions&quot;: {
		&quot;target&quot;: &quot;ES2020&quot;,
		&quot;lib&quot;: [&quot;ES2020&quot;, &quot;DOM&quot;, &quot;DOM.Iterable&quot;],
		&quot;module&quot;: &quot;ESNext&quot;,
		&quot;skipLibCheck&quot;: true,
		&quot;moduleResolution&quot;: &quot;bundler&quot;,
		&quot;allowImportingTsExtensions&quot;: true,
		&quot;rewriteRelativeImportExtensions&quot;: true,
		&quot;resolveJsonModule&quot;: true,
		&quot;isolatedModules&quot;: true,
		&quot;noEmit&quot;: true,
		&quot;jsx&quot;: &quot;react-jsx&quot;,
		&quot;strict&quot;: true,
		&quot;noUnusedLocals&quot;: true,
		&quot;noUnusedParameters&quot;: true,
		&quot;noFallthroughCasesInSwitch&quot;: true
	},
	&quot;include&quot;: [&quot;src&quot;, &quot;tests&quot;, &quot;frontend&quot;],
	&quot;exclude&quot;: [&quot;node_modules&quot;, &quot;dist&quot;]
}
">
<input type="hidden" name="project[files][turbo.json]" value="{
	&quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
	&quot;extends&quot;: [&quot;//&quot;],
	&quot;tasks&quot;: {
		&quot;build&quot;: {
			&quot;dependsOn&quot;: [&quot;@rivetkit/react#build&quot;, &quot;rivetkit#build&quot;]
		}
	}
}
">
<input type="hidden" name="project[files][vercel.json]" value="{
	&quot;framework&quot;: &quot;hono&quot;
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;
import srvx from &quot;vite-plugin-srvx&quot;;

export default defineConfig({
	plugins: [react(), ...srvx({ entry: &quot;src/server.ts&quot; })],
});
">
<input type="hidden" name="project[files][vitest.config.ts]" value="import { defineConfig } from &quot;vitest/config&quot;;

export default defineConfig({
	test: {
		environment: &quot;node&quot;,
	},
});
">
<input type="hidden" name="project[files][frontend/App.tsx]" value="import { createRivetKit } from &quot;@rivetkit/react&quot;;
import { useEffect, useState } from &quot;react&quot;;
import type { Player, registry } from &quot;../src/actors.ts&quot;;

const { useActor } = createRivetKit&lt;typeof registry&gt;(`${window.location.origin}/api/rivet`);

export function App() {
	const [region, setRegion] = useState(&quot;us-east&quot;);
	const [players, setPlayers] = useState&lt;Record&lt;string, Player&gt;&gt;({});
	const [myPlayerId, setMyPlayerId] = useState&lt;string | null&gt;(null);
	const [latency, setLatency] = useState&lt;number&gt;(0);
	const [currentRegion, setCurrentRegion] = useState&lt;string&gt;(&quot;&quot;);

	// Pass region parameter to useActor - this demonstrates multi-region deployment
	const actor = useActor({
		name: &quot;gameRoom&quot;,
		key: [&quot;main&quot;],
		// createInRegion isolates actor instances by region
		createInRegion: region,
		// createWithInput parameter is passed to createState
		createWithInput: { region },
	});

	// Track connection and get initial state
	useEffect(() =&gt; {
		if (!actor.connection) return;

		// Fetch initial game state
		actor.connection
			.getGameState()
			.then((state: { players: Record&lt;string, Player&gt;; region: string }) =&gt; {
				setPlayers(state.players);
				setCurrentRegion(state.region);
				// Set my player ID to one of the players (we&#39;ll update this when playerJoined event fires)
				const playerIds = Object.keys(state.players);
				if (playerIds.length &gt; 0 &amp;&amp; !myPlayerId) {
					setMyPlayerId(playerIds[playerIds.length - 1]);
				}
			})
			.catch((err: unknown) =&gt; console.error(&quot;Failed to get game state:&quot;, err));
	}, [actor.connection, myPlayerId]);

	// Handle keyboard input for movement
	useEffect(() =&gt; {
		if (!actor.connection) return;

		const handleKeyDown = (e: KeyboardEvent) =&gt; {
			let dx = 0;
			let dy = 0;

			switch (e.key) {
				case &quot;w&quot;:
				case &quot;ArrowUp&quot;:
					dy = -10;
					break;
				case &quot;s&quot;:
				case &quot;ArrowDown&quot;:
					dy = 10;
					break;
				case &quot;a&quot;:
				case &quot;ArrowLeft&quot;:
					dx = -10;
					break;
				case &quot;d&quot;:
				case &quot;ArrowRight&quot;:
					dx = 10;
					break;
			}

			if (dx !== 0 || dy !== 0) {
				const startTime = Date.now();
				actor.connection
					?.move(dx, dy)
					.then(() =&gt; {
						// Calculate round-trip latency
						setLatency(Date.now() - startTime);
					})
					.catch((err: unknown) =&gt; console.error(&quot;Move failed:&quot;, err));
			}
		};

		window.addEventListener(&quot;keydown&quot;, handleKeyDown);
		return () =&gt; window.removeEventListener(&quot;keydown&quot;, handleKeyDown);
	}, [actor.connection]);

	// Listen for player joined event
	actor.useEvent(
		&quot;playerJoined&quot;,
		({ playerId, player }: { playerId: string; player: Player }) =&gt; {
			setPlayers((prev) =&gt; ({ ...prev, [playerId]: player }));
			// Set our player ID if we don&#39;t have one yet
			if (!myPlayerId) {
				setMyPlayerId(playerId);
			}
		},
	);

	// Listen for player left event
	actor.useEvent(&quot;playerLeft&quot;, ({ playerId }: { playerId: string }) =&gt; {
		setPlayers((prev) =&gt; {
			const newPlayers = { ...prev };
			delete newPlayers[playerId];
			return newPlayers;
		});
	});

	// Listen for player movement
	actor.useEvent(
		&quot;playerMoved&quot;,
		({ playerId, x, y }: { playerId: string; x: number; y: number }) =&gt; {
			setPlayers((prev) =&gt; {
				const player = prev[playerId];
				if (!player) return prev;
				return {
					...prev,
					[playerId]: { ...player, x, y, lastUpdate: Date.now() },
				};
			});
		},
	);

	// Handle region change
	const handleRegionChange = (newRegion: string) =&gt; {
		setRegion(newRegion);
		setPlayers({});
		setMyPlayerId(null);
	};

	const playerCount = Object.keys(players).length;

	return (
		&lt;div className=&quot;app-container&quot;&gt;
			&lt;div className=&quot;connection-status-wrapper&quot;&gt;
				&lt;div
					className={`connection-status ${actor.connection ? &quot;connected&quot; : &quot;disconnected&quot;}`}
				&gt;
					{actor.connection ? &quot;Connected&quot; : &quot;Disconnected&quot;}
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div className=&quot;header&quot;&gt;
				&lt;h1&gt;Quickstart: Multi-Region&lt;/h1&gt;
				&lt;p&gt;Multiplayer game demonstrating multi-region deployment&lt;/p&gt;
			&lt;/div&gt;

			&lt;div className=&quot;info-box&quot;&gt;
				&lt;h3&gt;Multi-Region Deployment&lt;/h3&gt;
				&lt;p&gt;
					Select a region below to connect to a game room in that region. Each
					region has its own isolated set of actors, allowing players to connect
					to servers closer to them for lower latency.
				&lt;/p&gt;
			&lt;/div&gt;

			&lt;div className=&quot;region-selector&quot;&gt;
				&lt;label&gt;
					&lt;strong&gt;Select Region:&lt;/strong&gt;
				&lt;/label&gt;
				&lt;select value={region} onChange={(e) =&gt; handleRegionChange(e.target.value)}&gt;
					&lt;option value=&quot;us-east&quot;&gt;US East&lt;/option&gt;
					&lt;option value=&quot;eu-west&quot;&gt;EU West&lt;/option&gt;
					&lt;option value=&quot;ap-south&quot;&gt;AP South&lt;/option&gt;
				&lt;/select&gt;
			&lt;/div&gt;

			&lt;div className=&quot;region-info&quot;&gt;
				&lt;div className=&quot;info-card&quot;&gt;
					&lt;strong&gt;Current Region:&lt;/strong&gt; {currentRegion || region}
				&lt;/div&gt;
				&lt;div className=&quot;info-card&quot;&gt;
					&lt;strong&gt;Players in Region:&lt;/strong&gt; {playerCount}
				&lt;/div&gt;
				&lt;div className=&quot;info-card&quot;&gt;
					&lt;strong&gt;Latency:&lt;/strong&gt; {latency}ms
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div className=&quot;game-area&quot;&gt;
				&lt;svg
					width=&quot;600&quot;
					height=&quot;600&quot;
					className=&quot;game-canvas&quot;
					viewBox=&quot;0 0 1000 1000&quot;
				&gt;
					{/* Grid background */}
					&lt;defs&gt;
						&lt;pattern
							id=&quot;grid&quot;
							width=&quot;100&quot;
							height=&quot;100&quot;
							patternUnits=&quot;userSpaceOnUse&quot;
						&gt;
							&lt;path
								d=&quot;M 100 0 L 0 0 0 100&quot;
								fill=&quot;none&quot;
								stroke=&quot;#e0e0e0&quot;
								strokeWidth=&quot;1&quot;
							/&gt;
						&lt;/pattern&gt;
					&lt;/defs&gt;
					&lt;rect width=&quot;1000&quot; height=&quot;1000&quot; fill=&quot;url(#grid)&quot; /&gt;
					&lt;rect
						width=&quot;1000&quot;
						height=&quot;1000&quot;
						fill=&quot;none&quot;
						stroke=&quot;#333&quot;
						strokeWidth=&quot;3&quot;
					/&gt;

					{/* Render all players */}
					{Object.values(players).map((player) =&gt; {
						const isMe = player.id === myPlayerId;
						return (
							&lt;g key={player.id}&gt;
								{/* Player shadow */}
								&lt;circle
									cx={player.x + 2}
									cy={player.y + 2}
									r=&quot;12&quot;
									fill=&quot;rgba(0,0,0,0.2)&quot;
								/&gt;
								{/* Player */}
								&lt;circle
									cx={player.x}
									cy={player.y}
									r=&quot;10&quot;
									fill={player.color}
									stroke=&quot;#333&quot;
									strokeWidth=&quot;2&quot;
								/&gt;
								{/* Player label */}
								&lt;text
									x={player.x}
									y={player.y - 15}
									textAnchor=&quot;middle&quot;
									fontSize=&quot;12&quot;
									fill=&quot;#333&quot;
									fontWeight={isMe ? &quot;bold&quot; : &quot;normal&quot;}
								&gt;
									{isMe ? &quot;YOU&quot; : player.id.substring(0, 8)}
								&lt;/text&gt;
							&lt;/g&gt;
						);
					})}
				&lt;/svg&gt;
			&lt;/div&gt;

			&lt;div className=&quot;controls&quot;&gt;
				&lt;p&gt;
					&lt;strong&gt;Controls:&lt;/strong&gt;
				&lt;/p&gt;
				&lt;p&gt;Move: WASD or Arrow Keys&lt;/p&gt;
				&lt;p&gt;
					Each region has its own isolated game rooms. Players in different
					regions cannot see each other.
				&lt;/p&gt;
			&lt;/div&gt;
		&lt;/div&gt;
	);
}
">
<input type="hidden" name="project[files][frontend/main.tsx]" value="import { StrictMode } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
import { App } from &quot;./App.tsx&quot;;

const root = document.getElementById(&quot;root&quot;);
if (!root) throw new Error(&quot;Root element not found&quot;);

createRoot(root).render(
	&lt;StrictMode&gt;
		&lt;App /&gt;
	&lt;/StrictMode&gt;
);
">
<input type="hidden" name="project[files][tests/regions.test.ts]" value="import { setupTest } from &quot;rivetkit/test&quot;;
import { describe, expect, test } from &quot;vitest&quot;;
import { registry } from &quot;../src/actors.ts&quot;;

describe(&quot;multi-region deployment&quot;, () =&gt; {
	test(&quot;isolates actors by region&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		const usEast = client.gameRoom.getOrCreate([&quot;room1&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const euWest = client.gameRoom.getOrCreate([&quot;room1&quot;], {
			createInRegion: &quot;eu-west&quot;,
			createWithInput: { region: &quot;eu-west&quot; },
		});

		const usRegion = await usEast.getRegion();
		const euRegion = await euWest.getRegion();

		expect(usRegion).toBe(&quot;us-east&quot;);
		expect(euRegion).toBe(&quot;eu-west&quot;);

		// Verify they are different actor instances
		const usState = await usEast.getGameState();
		const euState = await euWest.getGameState();

		expect(usState.region).toBe(&quot;us-east&quot;);
		expect(euState.region).toBe(&quot;eu-west&quot;);
	});

	test(&quot;players isolated by region&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create actors in different regions
		const usEast = client.gameRoom.getOrCreate([&quot;room2&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const euWest = client.gameRoom.getOrCreate([&quot;room2&quot;], {
			createInRegion: &quot;eu-west&quot;,
			createWithInput: { region: &quot;eu-west&quot; },
		});

		// Get initial state
		const usInitialState = await usEast.getGameState();
		const euInitialState = await euWest.getGameState();

		// Verify rooms maintain separate player lists
		expect(usInitialState.players).not.toBe(euInitialState.players);
	});

	test(&quot;same room ID, different regions&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create &quot;room1&quot; in different regions
		const usRoom1 = client.gameRoom.getOrCreate([&quot;room1&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const euRoom1 = client.gameRoom.getOrCreate([&quot;room1&quot;], {
			createInRegion: &quot;eu-west&quot;,
			createWithInput: { region: &quot;eu-west&quot; },
		});

		// Verify they are different instances
		const usState = await usRoom1.getGameState();
		const euState = await euRoom1.getGameState();

		expect(usState.region).toBe(&quot;us-east&quot;);
		expect(euState.region).toBe(&quot;eu-west&quot;);

		// Verify state is not shared
		expect(usState).not.toBe(euState);
	});

	test(&quot;movement within region&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		const room = client.gameRoom.getOrCreate([&quot;room3&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		// Movement should work within region
		// Note: move requires connection context, so this tests the action exists
		expect(room.move).toBeDefined();
		expect(typeof room.move).toBe(&quot;function&quot;);
	});

	test(&quot;region parameter validation&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create actor with custom region
		const customRegion = client.gameRoom.getOrCreate([&quot;room4&quot;], {
			createInRegion: &quot;custom-region&quot;,
			createWithInput: { region: &quot;custom-region&quot; },
		});

		const region = await customRegion.getRegion();
		expect(region).toBe(&quot;custom-region&quot;);
	});

	test(&quot;region switching creates separate instances&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Connect to us-east
		const usEast = client.gameRoom.getOrCreate([&quot;room5&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const usState = await usEast.getGameState();
		expect(usState.region).toBe(&quot;us-east&quot;);

		// Connect to eu-west with same room ID
		const euWest = client.gameRoom.getOrCreate([&quot;room5&quot;], {
			createInRegion: &quot;eu-west&quot;,
			createWithInput: { region: &quot;eu-west&quot; },
		});

		const euState = await euWest.getGameState();
		expect(euState.region).toBe(&quot;eu-west&quot;);

		// Verify they have separate state
		expect(usState.region).not.toBe(euState.region);
	});

	test(&quot;multiple regions with different room IDs&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create different rooms in different regions
		const usRoom1 = client.gameRoom.getOrCreate([&quot;lobby&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const usRoom2 = client.gameRoom.getOrCreate([&quot;game&quot;], {
			createInRegion: &quot;us-east&quot;,
			createWithInput: { region: &quot;us-east&quot; },
		});

		const euRoom1 = client.gameRoom.getOrCreate([&quot;lobby&quot;], {
			createInRegion: &quot;eu-west&quot;,
			createWithInput: { region: &quot;eu-west&quot; },
		});

		// Verify all have correct regions
		expect(await usRoom1.getRegion()).toBe(&quot;us-east&quot;);
		expect(await usRoom2.getRegion()).toBe(&quot;us-east&quot;);
		expect(await euRoom1.getRegion()).toBe(&quot;eu-west&quot;);

		// Verify they are independent
		const usRoom1State = await usRoom1.getGameState();
		const usRoom2State = await usRoom2.getGameState();
		const euRoom1State = await euRoom1.getGameState();

		expect(usRoom1State.players).not.toBe(usRoom2State.players);
		expect(usRoom1State.players).not.toBe(euRoom1State.players);
	});

	test(&quot;getGameState returns players and region&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		const room = client.gameRoom.getOrCreate([&quot;test-state&quot;], {
			createInRegion: &quot;ap-south&quot;,
			createWithInput: { region: &quot;ap-south&quot; },
		});

		const state = await room.getGameState();

		// Verify structure
		expect(state).toHaveProperty(&quot;players&quot;);
		expect(state).toHaveProperty(&quot;region&quot;);
		expect(typeof state.players).toBe(&quot;object&quot;);
		expect(state.region).toBe(&quot;ap-south&quot;);
	});
});
">
<input type="hidden" name="project[files][src/actors.ts]" value="import { actor, setup } from &quot;rivetkit&quot;;
import type { Player } from &quot;./types.ts&quot;;

export type { Player };

interface State {
	players: Record&lt;string, Player&gt;;
	region: string;
}

const gameRoom = actor({
	// Create initial state with region parameter
	createState: (_c, input: { region: string }): State =&gt; ({
		players: {} as Record&lt;string, Player&gt;,
		region: input.region,
	}),

	// Connection state - tracks which player belongs to each connection
	connState: {
		playerId: null as string | null,
	},

	// Handle client connections
	onConnect: (c, conn) =&gt; {
		// Generate a unique player ID
		const playerId = conn.id;

		// Generate random color for player
		const colors = [
			&quot;#FF6B6B&quot;,
			&quot;#4ECDC4&quot;,
			&quot;#45B7D1&quot;,
			&quot;#FFA07A&quot;,
			&quot;#98D8C8&quot;,
			&quot;#F7DC6F&quot;,
			&quot;#BB8FCE&quot;,
			&quot;#85C1E2&quot;,
		];
		const color = colors[Math.floor(Math.random() * colors.length)];

		// Set connection state
		conn.state.playerId = playerId;

		// Add player at random position
		c.state.players[playerId] = {
			id: playerId,
			x: Math.floor(Math.random() * 900) + 50,
			y: Math.floor(Math.random() * 900) + 50,
			color,
			lastUpdate: Date.now(),
		};

		// Broadcast player joined event
		c.broadcast(&quot;playerJoined&quot;, {
			playerId,
			player: c.state.players[playerId],
		});
	},

	onDisconnect: (c, conn) =&gt; {
		const playerId = conn.state.playerId;
		if (playerId) {
			delete c.state.players[playerId];
			c.broadcast(&quot;playerLeft&quot;, { playerId });
		}
	},

	actions: {
		// Move player by delta
		move: (c, dx: number, dy: number) =&gt; {
			const playerId = c.conn.state.playerId;
			if (!playerId) return { x: 0, y: 0 };

			const player = c.state.players[playerId];
			if (!player) return { x: 0, y: 0 };

			// Update position
			player.x += dx;
			player.y += dy;

			// Keep player in bounds (0-1000)
			player.x = Math.max(0, Math.min(player.x, 1000));
			player.y = Math.max(0, Math.min(player.y, 1000));

			// Update timestamp
			player.lastUpdate = Date.now();

			// Broadcast movement
			c.broadcast(&quot;playerMoved&quot;, {
				playerId,
				x: player.x,
				y: player.y,
			});

			return { x: player.x, y: player.y };
		},

		// Get current game state
		getGameState: (c) =&gt; {
			return {
				players: c.state.players,
				region: c.state.region,
			};
		},

		// Get region info
		getRegion: (c) =&gt; {
			return c.state.region;
		},
	},
});

// Register actors for use
export const registry = setup({
	use: { gameRoom },
});
">
<input type="hidden" name="project[files][src/server.ts]" value="import { Hono } from &quot;hono&quot;;
import { registry } from &quot;./actors.ts&quot;;

const app = new Hono();
app.all(&quot;/api/rivet/*&quot;, (c) =&gt; registry.handler(c.req.raw));
export default app;
">
<input type="hidden" name="project[files][src/types.ts]" value="export type Position = { x: number; y: number };

export type Player = {
	id: string;
	x: number;
	y: number;
	color: string;
	lastUpdate: number;
};

export type GameState = {
	players: Record&lt;string, Player&gt;;
	region: string;
};

export type ConnectionState = {
	playerId: string | null;
};
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="multi-region">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>