The lobby package owns every per-room lobby surface in openturn — wire protocol, server runtime, React UI, bot registry, and bot supervisors. A singleDocumentation Index
Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
Use this file to discover all available pages before exploring further.
<LobbyWithBots> component renders against an in-memory channel for local single-device play and against a WebSocket channel for hosted multiplayer; the same LobbyRuntime powers both. Game authors plug a typed BotRegistry into defineGame once and per-seat bot dropdowns appear automatically.
Install
@openturn/core, @openturn/protocol, @openturn/server, @openturn/client, and @openturn/bot. React is a peer dep — only the ./react subpath touches the DOM.
Subpath exports
./registry, ./protocol, ./runtime, and ./supervisor are written without DOM globals so worker-runtime packages (cloud Durable Objects, the OSS dev server) can import them without pulling React.
@openturn/lobby/registry
Typed bot catalog. The registry is the single source of truth that the lobby UI, the server runtime, the deployment manifest, and the supervisor all read from.
defineBotRegistry(entries)
botID uniqueness at definition time. Different difficulties are distinct bot instances each with their own botID — there is no runtime-tunable param.
BotDescriptor<TGame>
BotRegistry<TGame>
findBot(registry, botID)
null when the botID is unknown. Used by the lobby UI to render labels and by the supervisor to wire the chosen bot into the session.
attachBots(game, registry)
game with the registry attached at game.bots. The engine ignores this field — the lobby runtime, the deploy step, and the supervisor read it as the single source of truth for “what bots can occupy a seat in this game”.
Why a helper instead of inlining defineGame({ bots }): registries usually live in a sibling package that imports the game’s types, which would create a circular package dep if the game also imported the registry. Keep the cycle one-way — the game stays bot-free, and the bots package re-exports a gameWithBots value built via this helper.
buildKnownBots(registry)
LobbyEnv.knownBots map the runtime accepts at construction time. Used internally by useLocalLobbyChannel and by the OSS dev server when wiring a LobbyRuntime for a game with bots set.
@openturn/lobby/protocol
Wire-protocol extensions layered on @openturn/protocol/lobby. Additive and back-compat — pre-@openturn/lobby clients/servers parse new messages with default [] for availableBots and playerAssignments.
LobbySeat (discriminated union)
{ userID | null, ready, … } shape. Existing code that destructures seat.userID must narrow on seat.kind first.
LobbyAvailableBot
lobby:state.availableBots so non-host clients can render labels for bot seats without bundling the registry.
Client → server messages (host-only)
clearSeat works on any seat kind — the host can use it to kick a human or remove a bot.
LobbyTransitionToGameMessage.playerAssignments
Rejection reasons
New values added toLobbyRejectionReason:
seat_has_bot—take_seaton a bot-occupied seat.seat_has_human—assign_boton a human-occupied seat.unknown_bot—assign_botwith abotIDnot inLobbyEnv.knownBots.
@openturn/lobby/runtime
The server-side state machine. Re-exports LobbyRuntime from @openturn/server and adds bot-aware LobbyEnv. Worker-safe.
LobbyEnv
knownBots is set, lobby:state.availableBots is populated from it and lobby:assign_bot is validated against it. Construct via buildKnownBots(registry).
LobbyRuntime methods
takeSeat()rejectsseat_out_of_rangewhenseatIndex >= targetCapacity,seat_has_botwhen the seat is bot-occupied.setReady()ignores bot seats (they read as always ready).setTargetCapacity()rejectstarget_below_min,target_above_max, orbad_target. Lowering capacity evicts seats whoseseatIndex >= targetCapacity(humans become unseated; bots are removed).start()requires seated count>= minPlayersand every human ready. Bots count toward the seated total. ReturnsLobbyStartResultwith the assignments map for actually-seated players.dropUser()/pruneToConnected()leave bot seats untouched.
LobbyStartAssignment
runtime.start(...) and used to construct the lobby:transition_to_game payload.
@openturn/lobby/react
The UI surface. All exports require React 19+ (peer dep).
<Lobby> and <LobbyWithBots>
Two components over the same LobbyView:
<Lobby>— the round-table seat list with claim/leave/ready/start. No bot affordances. Use this when the game has noBotRegistryor you want the legacy UI.<LobbyWithBots>—<Lobby>with each seat replaced by<LobbySeatControl>, which adds an “Assign bot ▾” dropdown on open seats and a ”🤖 Bot · ” chip on bot seats (with a “Clear” link for host viewers).
<LobbyWithBots> reads the bot catalog from lobby.availableBots — apps don’t have to thread the registry into the UI.
useLocalLobbyChannel(options)
In-memory LobbyChannelHandle for single-device play. No WebSocket, no token. Renders the same <LobbyWithBots> UI.
LobbyRuntime in-process, auto-seats the host on mount (single user in local play), and emits a synthetic lobby:transition_to_game on start() with empty roomToken/websocketURL.
useLobbyChannel(options)
Hosted (WebSocket-backed) channel. Already-existing API — re-exported here so apps can write import { useLobbyChannel } from "@openturn/lobby/react" regardless of whether they target local or hosted play.
buildLobbyView({ channel, userID, … })
Adapts a LobbyChannelHandle into a flat LobbyView consumable by <Lobby> / <LobbyWithBots>. Adds assignBot(seatIndex, botID) and clearSeat(seatIndex) action callbacks alongside the existing takeSeat / leaveSeat / setReady / start.
useBotAttachOnTransition(options)
Wires attachLocalBots into a freshly-created session as soon as channel.transition arrives. Returns a bot-aware session facade — drive your game UI with the facade, not the raw session.
null until both the transition has arrived and the session is provided. Cleans up bot runners on unmount or transition reset.
For app code that already uses createOpenturnBindings({ runtime: "local" }) and wants bot moves to flow through the same matchStore (so the inspector timeline tracks them), wire bots manually via a small useEffect keyed on snapshot instead — see How-to: play against bots for the pattern.
<LobbySeatControl>
The per-seat dropdown component used internally by <LobbyWithBots>. Exposed for apps that want to keep the round-table chrome but customise behaviour.
@openturn/lobby/supervisor
Two implementations of one shape: connect bot seats to a freshly-started match.
BotSupervisor
start() is one-shot — calling twice throws. stop() is idempotent.
createLocalBotSupervisor(options)
In-process supervisor. Calls attachLocalBots once per assignment when start() runs. After start, supervisor.getSession() returns the bot-aware facade.
unknown botID "..." if the assignment references a botID not in the registry.
createHostedBotSupervisor(options)
Per-bot-seat HostedClient supervisor. Used by the OSS dev server and the cloud Durable Object. Each bot seat opens its own connection back to the room using its own short-lived token.
LobbyRuntime.start() returns — they never reach client browsers.
For the cloud Durable Object case, a third pattern — direct in-DO dispatch — avoids opening WebSocket-to-self connections; see the Cloud DO bot driver section in @openturn/server for that variant. createHostedBotSupervisor is the right choice when bots run in a separate process from the room (the standard sidecar topology).
Game-author hooks
@openturn/lobby adds one optional, engine-inert field to GameDefinition:
attachBots(game, registry) (preferred) or by passing bots directly to defineGame. The engine never reads this field. The lobby runtime, the deploy step, and the supervisor all do.
Manifest integration
When a deployment is built,@openturn/deploy extracts game.bots.entries into manifest.multiplayer.availableBots so the cloud lobby UI can render dropdowns without bundling the registry. The cloud Durable Object reads the manifest at room-construction time and feeds it into LobbyEnv.knownBots.
Migration from @openturn/react
@openturn/react re-exports the lobby surface from @openturn/lobby/react for back-compat. Existing code that imports useLobbyChannel, buildLobbyView, or <Lobby> from @openturn/react continues to work unchanged.
For new code, prefer @openturn/lobby/react:
LobbySeat shape is the only typed-union breaking change. Code that destructured seat.userID directly must narrow on seat.kind === "human" first.
Related
- How-to: build a lobby — wire
<LobbyWithBots>end-to-end. - How-to: play against bots — pick bots in the lobby UI.
- Concepts: AI bots — design rationale for the bot layer.
- Reference:
@openturn/bot— bot authoring and runner API. - Reference:
@openturn/server—LobbyRuntimeand the in-DOBotDriver.