@remix-gg/sdk 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,9 +1,101 @@
1
+ /**
2
+ * `sdk.realtime` — realtime multiplayer rooms for Remix Desktop games.
3
+ *
4
+ * The game sees rooms, peers, and messages; everything else is deliberately
5
+ * invisible. The platform's room broker (Remix Desktop main) owns identity,
6
+ * room admission, signaling, and ICE minting; this controller rides the
7
+ * `multiplayer_*` game events both ways and drives the data-channel mesh
8
+ * (`./mesh.ts`) under the surface. A game never touches `RTCPeerConnection`,
9
+ * SDP, or ICE — which is what lets the transport change under a published
10
+ * game without the game updating.
11
+ *
12
+ * Membership truth is the SERVER ROSTER the broker relays
13
+ * (`multiplayer_peers`), never the mesh: a rebuilt slot re-fires its
14
+ * transport callbacks and a repeated bye can announce the same departure
15
+ * twice, so peer join/leave here is a roster diff and nothing else. The
16
+ * mesh's own callbacks are diagnostics.
17
+ */
18
+ type RealtimePeer = {
19
+ userId: string;
20
+ username: string;
21
+ pfp: string | null;
22
+ /** ISO seat stamp; seat order (oldest first) is the deterministic tiebreak. */
23
+ joinedAt: string | null;
24
+ };
25
+ type RealtimeEndReason = 'left' | 'expired' | 'offline' | 'closed';
26
+ type RealtimeErrorCode = 'room_not_found' | 'room_full' | 'game_mismatch' | 'no_game_id' | 'join_failed';
27
+ declare class RealtimeRoomError extends Error {
28
+ readonly code: RealtimeErrorCode;
29
+ constructor(code: RealtimeErrorCode, message: string);
30
+ }
31
+ type RealtimeSendOptions = {
32
+ /**
33
+ * Default true: ordered, retransmitted — lobby, countdowns, results.
34
+ * `reliable: false` is the per-frame state stream (unordered, never
35
+ * retransmitted): a packet that arrives late is worth less than nothing,
36
+ * and one that never arrives is replaced by the next frame anyway.
37
+ */
38
+ reliable?: boolean;
39
+ };
40
+ interface RealtimeRoom {
41
+ readonly roomId: string;
42
+ /** The join code — what the host shares with friends. */
43
+ readonly code: string;
44
+ readonly selfId: string;
45
+ readonly hostUserId: string;
46
+ /** Whether this player created the room. */
47
+ readonly isHost: boolean;
48
+ /** The live roster, self excluded, in seat order. */
49
+ readonly peers: RealtimePeer[];
50
+ readonly ended: boolean;
51
+ /** Broadcast to every peer. Data must be JSON-serializable. */
52
+ send(data: unknown, options?: RealtimeSendOptions): void;
53
+ sendTo(userId: string, data: unknown, options?: RealtimeSendOptions): void;
54
+ onMessage(callback: (fromUserId: string, data: unknown) => void): () => void;
55
+ onPeerJoin(callback: (peer: RealtimePeer) => void): () => void;
56
+ onPeerLeave(callback: (peer: RealtimePeer) => void): () => void;
57
+ onEnded(callback: (reason: RealtimeEndReason) => void): () => void;
58
+ /** Transport trouble worth telling the player about; the room keeps trying. */
59
+ onError(callback: (message: string) => void): () => void;
60
+ /**
61
+ * Open the platform's invite share flow — the friend picker that sends the
62
+ * room's join code into DMs and groups. The game never sees the friend
63
+ * list; the platform owns the whole exchange. A game may also just show
64
+ * `room.code` for players to share by hand.
65
+ */
66
+ invite(): void;
67
+ leave(): void;
68
+ }
69
+ interface RealtimeNamespace {
70
+ /** Create a room for this game and take the first seat. One live room at a time. */
71
+ createRoom(): Promise<RealtimeRoom>;
72
+ /** Join a friend's room by its invite code. */
73
+ joinRoom(code: string): Promise<RealtimeRoom>;
74
+ /** The live room, or null. */
75
+ readonly room: RealtimeRoom | null;
76
+ /**
77
+ * Every room that becomes live — including one the PLATFORM joined for the
78
+ * player (accepting an invite boots the game already seated). A lobby
79
+ * screen should mount from here, not only from its own create/join call.
80
+ */
81
+ onRoom(callback: (room: RealtimeRoom) => void): () => void;
82
+ /**
83
+ * A room the platform tried to join for the player (an accepted invite)
84
+ * that failed — full, expired, or the wrong game. There is no pending
85
+ * `createRoom`/`joinRoom` call to reject, so this is where the failure
86
+ * surfaces; a lobby should tell the player rather than sit solo as if
87
+ * nothing happened. Requested joins keep rejecting their own promise.
88
+ */
89
+ onRoomError(callback: (error: RealtimeRoomError) => void): () => void;
90
+ }
91
+
1
92
  declare global {
2
93
  interface Window {
3
94
  FarcadeSDK: typeof sdk;
4
95
  RemixSDK: typeof sdk;
5
96
  }
6
97
  }
98
+
7
99
  type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
8
100
  type SafeAreaInset = {
9
101
  top: number;
@@ -42,6 +134,16 @@ type GameInfo = {
42
134
  id: string;
43
135
  gameState: GameState;
44
136
  } | null;
137
+ /**
138
+ * Explicit turn owner at session start for turn-based multiplayer sessions:
139
+ * the id of the player the platform is waiting on (the challenge creator
140
+ * until they make their first move). Games should trust this over inferring
141
+ * the boot turn from their own state — the platform rejects state saves from
142
+ * anyone else. It reflects the moment game_info was sent; after the local
143
+ * player submits a move, the turn is theirs no longer. Absent outside
144
+ * challenge sessions and on hosts that predate the field.
145
+ */
146
+ usersTurnId?: string | null;
45
147
  /**
46
148
  * Present only when the host has level progression enabled for this session.
47
149
  * Games must key level mode off this block's presence, not their own level
@@ -165,7 +267,89 @@ type PurchaseCompleteEvent = {
165
267
  item?: string;
166
268
  };
167
269
  };
168
- type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent;
270
+ type RealtimeSignalKind = 'offer' | 'answer' | 'ice' | 'bye';
271
+ type MultiplayerCreateRoomEvent = {
272
+ type: 'multiplayer_create_room';
273
+ data: undefined;
274
+ };
275
+ type MultiplayerJoinRoomEvent = {
276
+ type: 'multiplayer_join_room';
277
+ data: {
278
+ code: string;
279
+ };
280
+ };
281
+ type MultiplayerLeaveRoomEvent = {
282
+ type: 'multiplayer_leave_room';
283
+ data: undefined;
284
+ };
285
+ type MultiplayerSignalEvent = {
286
+ type: 'multiplayer_signal';
287
+ data: {
288
+ toUserId: string;
289
+ kind: RealtimeSignalKind;
290
+ payload: string;
291
+ };
292
+ };
293
+ type MultiplayerRefreshIceEvent = {
294
+ type: 'multiplayer_refresh_ice';
295
+ data: undefined;
296
+ };
297
+ /**
298
+ * Open the platform's invite share flow for the live room. Carries nothing:
299
+ * the platform already knows the room's game and code.
300
+ */
301
+ type MultiplayerRequestInviteEvent = {
302
+ type: 'multiplayer_request_invite';
303
+ data: undefined;
304
+ };
305
+ type MultiplayerSessionEvent = {
306
+ type: 'multiplayer_session';
307
+ data: {
308
+ roomId: string;
309
+ code: string;
310
+ gameId: string;
311
+ selfId: string;
312
+ hostUserId: string;
313
+ peers: RealtimePeer[];
314
+ iceServers: RTCIceServer[];
315
+ };
316
+ };
317
+ type MultiplayerPeersEvent = {
318
+ type: 'multiplayer_peers';
319
+ data: {
320
+ peers: RealtimePeer[];
321
+ };
322
+ };
323
+ type MultiplayerSignalsEvent = {
324
+ type: 'multiplayer_signals';
325
+ data: {
326
+ signals: Array<{
327
+ fromUserId: string;
328
+ kind: string;
329
+ payload: string;
330
+ }>;
331
+ };
332
+ };
333
+ type MultiplayerIceServersEvent = {
334
+ type: 'multiplayer_ice_servers';
335
+ data: {
336
+ iceServers: RTCIceServer[];
337
+ };
338
+ };
339
+ type MultiplayerSessionEndedEvent = {
340
+ type: 'multiplayer_session_ended';
341
+ data: {
342
+ reason: RealtimeEndReason;
343
+ };
344
+ };
345
+ type MultiplayerErrorEvent = {
346
+ type: 'multiplayer_error';
347
+ data: {
348
+ code: RealtimeErrorCode;
349
+ message: string;
350
+ };
351
+ };
352
+ type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent | MultiplayerCreateRoomEvent | MultiplayerJoinRoomEvent | MultiplayerLeaveRoomEvent | MultiplayerSignalEvent | MultiplayerRefreshIceEvent | MultiplayerRequestInviteEvent | MultiplayerSessionEvent | MultiplayerPeersEvent | MultiplayerSignalsEvent | MultiplayerIceServersEvent | MultiplayerSessionEndedEvent | MultiplayerErrorEvent;
169
353
  type GameEventMessage<T extends GameEvent['type']> = {
170
354
  type: 'game_event';
171
355
  event: Extract<GameEvent, {
@@ -175,8 +359,8 @@ type GameEventMessage<T extends GameEvent['type']> = {
175
359
  /**
176
360
  * Messages from the game host to the game client
177
361
  */
178
- type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
179
- type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
362
+ type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete' | 'multiplayer_session' | 'multiplayer_peers' | 'multiplayer_signals' | 'multiplayer_ice_servers' | 'multiplayer_session_ended' | 'multiplayer_error'>;
363
+ type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase' | 'multiplayer_create_room' | 'multiplayer_join_room' | 'multiplayer_leave_room' | 'multiplayer_signal' | 'multiplayer_refresh_ice' | 'multiplayer_request_invite'>;
180
364
  type EventCallback = (data: unknown) => void;
181
365
  declare class RemixSDK {
182
366
  /**
@@ -256,6 +440,15 @@ declare class RemixSDK {
256
440
  purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
257
441
  };
258
442
  };
443
+ /**
444
+ * Realtime multiplayer rooms (Remix Desktop): create or join a room, then
445
+ * talk to peers over `room.send` / `room.onMessage`. Distinct from
446
+ * `sdk.multiplayer`, which is the platform-arbitrated turn-based flow.
447
+ * The transport underneath is the platform's business; games see rooms,
448
+ * peers, and messages, and nothing else.
449
+ */
450
+ private realtimeController;
451
+ realtime: RealtimeNamespace;
259
452
  private emit;
260
453
  private handleMessage;
261
454
  private sendMessage;
@@ -265,4 +458,4 @@ declare class RemixSDK {
265
458
  }
266
459
  declare const sdk: RemixSDK;
267
460
 
268
- export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerGameOverEvent, type MultiplayerSaveGameStateEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
461
+ export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerCreateRoomEvent, type MultiplayerErrorEvent, type MultiplayerGameOverEvent, type MultiplayerIceServersEvent, type MultiplayerJoinRoomEvent, type MultiplayerLeaveRoomEvent, type MultiplayerPeersEvent, type MultiplayerRefreshIceEvent, type MultiplayerRequestInviteEvent, type MultiplayerSaveGameStateEvent, type MultiplayerSessionEndedEvent, type MultiplayerSessionEvent, type MultiplayerSignalEvent, type MultiplayerSignalsEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RealtimeEndReason, type RealtimeErrorCode, type RealtimeNamespace, type RealtimePeer, type RealtimeRoom, RealtimeRoomError, type RealtimeSendOptions, type RealtimeSignalKind, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,101 @@
1
+ /**
2
+ * `sdk.realtime` — realtime multiplayer rooms for Remix Desktop games.
3
+ *
4
+ * The game sees rooms, peers, and messages; everything else is deliberately
5
+ * invisible. The platform's room broker (Remix Desktop main) owns identity,
6
+ * room admission, signaling, and ICE minting; this controller rides the
7
+ * `multiplayer_*` game events both ways and drives the data-channel mesh
8
+ * (`./mesh.ts`) under the surface. A game never touches `RTCPeerConnection`,
9
+ * SDP, or ICE — which is what lets the transport change under a published
10
+ * game without the game updating.
11
+ *
12
+ * Membership truth is the SERVER ROSTER the broker relays
13
+ * (`multiplayer_peers`), never the mesh: a rebuilt slot re-fires its
14
+ * transport callbacks and a repeated bye can announce the same departure
15
+ * twice, so peer join/leave here is a roster diff and nothing else. The
16
+ * mesh's own callbacks are diagnostics.
17
+ */
18
+ type RealtimePeer = {
19
+ userId: string;
20
+ username: string;
21
+ pfp: string | null;
22
+ /** ISO seat stamp; seat order (oldest first) is the deterministic tiebreak. */
23
+ joinedAt: string | null;
24
+ };
25
+ type RealtimeEndReason = 'left' | 'expired' | 'offline' | 'closed';
26
+ type RealtimeErrorCode = 'room_not_found' | 'room_full' | 'game_mismatch' | 'no_game_id' | 'join_failed';
27
+ declare class RealtimeRoomError extends Error {
28
+ readonly code: RealtimeErrorCode;
29
+ constructor(code: RealtimeErrorCode, message: string);
30
+ }
31
+ type RealtimeSendOptions = {
32
+ /**
33
+ * Default true: ordered, retransmitted — lobby, countdowns, results.
34
+ * `reliable: false` is the per-frame state stream (unordered, never
35
+ * retransmitted): a packet that arrives late is worth less than nothing,
36
+ * and one that never arrives is replaced by the next frame anyway.
37
+ */
38
+ reliable?: boolean;
39
+ };
40
+ interface RealtimeRoom {
41
+ readonly roomId: string;
42
+ /** The join code — what the host shares with friends. */
43
+ readonly code: string;
44
+ readonly selfId: string;
45
+ readonly hostUserId: string;
46
+ /** Whether this player created the room. */
47
+ readonly isHost: boolean;
48
+ /** The live roster, self excluded, in seat order. */
49
+ readonly peers: RealtimePeer[];
50
+ readonly ended: boolean;
51
+ /** Broadcast to every peer. Data must be JSON-serializable. */
52
+ send(data: unknown, options?: RealtimeSendOptions): void;
53
+ sendTo(userId: string, data: unknown, options?: RealtimeSendOptions): void;
54
+ onMessage(callback: (fromUserId: string, data: unknown) => void): () => void;
55
+ onPeerJoin(callback: (peer: RealtimePeer) => void): () => void;
56
+ onPeerLeave(callback: (peer: RealtimePeer) => void): () => void;
57
+ onEnded(callback: (reason: RealtimeEndReason) => void): () => void;
58
+ /** Transport trouble worth telling the player about; the room keeps trying. */
59
+ onError(callback: (message: string) => void): () => void;
60
+ /**
61
+ * Open the platform's invite share flow — the friend picker that sends the
62
+ * room's join code into DMs and groups. The game never sees the friend
63
+ * list; the platform owns the whole exchange. A game may also just show
64
+ * `room.code` for players to share by hand.
65
+ */
66
+ invite(): void;
67
+ leave(): void;
68
+ }
69
+ interface RealtimeNamespace {
70
+ /** Create a room for this game and take the first seat. One live room at a time. */
71
+ createRoom(): Promise<RealtimeRoom>;
72
+ /** Join a friend's room by its invite code. */
73
+ joinRoom(code: string): Promise<RealtimeRoom>;
74
+ /** The live room, or null. */
75
+ readonly room: RealtimeRoom | null;
76
+ /**
77
+ * Every room that becomes live — including one the PLATFORM joined for the
78
+ * player (accepting an invite boots the game already seated). A lobby
79
+ * screen should mount from here, not only from its own create/join call.
80
+ */
81
+ onRoom(callback: (room: RealtimeRoom) => void): () => void;
82
+ /**
83
+ * A room the platform tried to join for the player (an accepted invite)
84
+ * that failed — full, expired, or the wrong game. There is no pending
85
+ * `createRoom`/`joinRoom` call to reject, so this is where the failure
86
+ * surfaces; a lobby should tell the player rather than sit solo as if
87
+ * nothing happened. Requested joins keep rejecting their own promise.
88
+ */
89
+ onRoomError(callback: (error: RealtimeRoomError) => void): () => void;
90
+ }
91
+
1
92
  declare global {
2
93
  interface Window {
3
94
  FarcadeSDK: typeof sdk;
4
95
  RemixSDK: typeof sdk;
5
96
  }
6
97
  }
98
+
7
99
  type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
8
100
  type SafeAreaInset = {
9
101
  top: number;
@@ -42,6 +134,16 @@ type GameInfo = {
42
134
  id: string;
43
135
  gameState: GameState;
44
136
  } | null;
137
+ /**
138
+ * Explicit turn owner at session start for turn-based multiplayer sessions:
139
+ * the id of the player the platform is waiting on (the challenge creator
140
+ * until they make their first move). Games should trust this over inferring
141
+ * the boot turn from their own state — the platform rejects state saves from
142
+ * anyone else. It reflects the moment game_info was sent; after the local
143
+ * player submits a move, the turn is theirs no longer. Absent outside
144
+ * challenge sessions and on hosts that predate the field.
145
+ */
146
+ usersTurnId?: string | null;
45
147
  /**
46
148
  * Present only when the host has level progression enabled for this session.
47
149
  * Games must key level mode off this block's presence, not their own level
@@ -165,7 +267,89 @@ type PurchaseCompleteEvent = {
165
267
  item?: string;
166
268
  };
167
269
  };
168
- type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent;
270
+ type RealtimeSignalKind = 'offer' | 'answer' | 'ice' | 'bye';
271
+ type MultiplayerCreateRoomEvent = {
272
+ type: 'multiplayer_create_room';
273
+ data: undefined;
274
+ };
275
+ type MultiplayerJoinRoomEvent = {
276
+ type: 'multiplayer_join_room';
277
+ data: {
278
+ code: string;
279
+ };
280
+ };
281
+ type MultiplayerLeaveRoomEvent = {
282
+ type: 'multiplayer_leave_room';
283
+ data: undefined;
284
+ };
285
+ type MultiplayerSignalEvent = {
286
+ type: 'multiplayer_signal';
287
+ data: {
288
+ toUserId: string;
289
+ kind: RealtimeSignalKind;
290
+ payload: string;
291
+ };
292
+ };
293
+ type MultiplayerRefreshIceEvent = {
294
+ type: 'multiplayer_refresh_ice';
295
+ data: undefined;
296
+ };
297
+ /**
298
+ * Open the platform's invite share flow for the live room. Carries nothing:
299
+ * the platform already knows the room's game and code.
300
+ */
301
+ type MultiplayerRequestInviteEvent = {
302
+ type: 'multiplayer_request_invite';
303
+ data: undefined;
304
+ };
305
+ type MultiplayerSessionEvent = {
306
+ type: 'multiplayer_session';
307
+ data: {
308
+ roomId: string;
309
+ code: string;
310
+ gameId: string;
311
+ selfId: string;
312
+ hostUserId: string;
313
+ peers: RealtimePeer[];
314
+ iceServers: RTCIceServer[];
315
+ };
316
+ };
317
+ type MultiplayerPeersEvent = {
318
+ type: 'multiplayer_peers';
319
+ data: {
320
+ peers: RealtimePeer[];
321
+ };
322
+ };
323
+ type MultiplayerSignalsEvent = {
324
+ type: 'multiplayer_signals';
325
+ data: {
326
+ signals: Array<{
327
+ fromUserId: string;
328
+ kind: string;
329
+ payload: string;
330
+ }>;
331
+ };
332
+ };
333
+ type MultiplayerIceServersEvent = {
334
+ type: 'multiplayer_ice_servers';
335
+ data: {
336
+ iceServers: RTCIceServer[];
337
+ };
338
+ };
339
+ type MultiplayerSessionEndedEvent = {
340
+ type: 'multiplayer_session_ended';
341
+ data: {
342
+ reason: RealtimeEndReason;
343
+ };
344
+ };
345
+ type MultiplayerErrorEvent = {
346
+ type: 'multiplayer_error';
347
+ data: {
348
+ code: RealtimeErrorCode;
349
+ message: string;
350
+ };
351
+ };
352
+ type GameEvent = PlayAgainEvent | SinglePlayerGameOverEvent | ReadyEvent | HapticFeedbackEvent | ToggleMuteEvent | GameErrorEvent | SaveGameStateEvent | RefuteGameStateEvent | GameInfoEvent | GameStateUpdatedEvent | MultiplayerGameOverEvent | MultiplayerSaveGameStateEvent | PurchaseEvent | PurchaseCompleteEvent | MultiplayerCreateRoomEvent | MultiplayerJoinRoomEvent | MultiplayerLeaveRoomEvent | MultiplayerSignalEvent | MultiplayerRefreshIceEvent | MultiplayerRequestInviteEvent | MultiplayerSessionEvent | MultiplayerPeersEvent | MultiplayerSignalsEvent | MultiplayerIceServersEvent | MultiplayerSessionEndedEvent | MultiplayerErrorEvent;
169
353
  type GameEventMessage<T extends GameEvent['type']> = {
170
354
  type: 'game_event';
171
355
  event: Extract<GameEvent, {
@@ -175,8 +359,8 @@ type GameEventMessage<T extends GameEvent['type']> = {
175
359
  /**
176
360
  * Messages from the game host to the game client
177
361
  */
178
- type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'>;
179
- type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase'>;
362
+ type IncomingGameEvent = GameEventMessage<'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete' | 'multiplayer_session' | 'multiplayer_peers' | 'multiplayer_signals' | 'multiplayer_ice_servers' | 'multiplayer_session_ended' | 'multiplayer_error'>;
363
+ type OutgoingGameEvent = GameEventMessage<'game_over' | 'ready' | 'haptic_feedback' | 'error' | 'save_game_state' | 'refute_game_state' | 'multiplayer_game_over' | 'multiplayer_save_game_state' | 'purchase' | 'multiplayer_create_room' | 'multiplayer_join_room' | 'multiplayer_leave_room' | 'multiplayer_signal' | 'multiplayer_refresh_ice' | 'multiplayer_request_invite'>;
180
364
  type EventCallback = (data: unknown) => void;
181
365
  declare class RemixSDK {
182
366
  /**
@@ -256,6 +440,15 @@ declare class RemixSDK {
256
440
  purchase: (data: PurchaseEvent["data"]) => Promise<PurchaseCompleteEvent["data"]>;
257
441
  };
258
442
  };
443
+ /**
444
+ * Realtime multiplayer rooms (Remix Desktop): create or join a room, then
445
+ * talk to peers over `room.send` / `room.onMessage`. Distinct from
446
+ * `sdk.multiplayer`, which is the platform-arbitrated turn-based flow.
447
+ * The transport underneath is the platform's business; games see rooms,
448
+ * peers, and messages, and nothing else.
449
+ */
450
+ private realtimeController;
451
+ realtime: RealtimeNamespace;
259
452
  private emit;
260
453
  private handleMessage;
261
454
  private sendMessage;
@@ -265,4 +458,4 @@ declare class RemixSDK {
265
458
  }
266
459
  declare const sdk: RemixSDK;
267
460
 
268
- export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerGameOverEvent, type MultiplayerSaveGameStateEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };
461
+ export { type GameErrorEvent, type GameEvent, type GameEventMessage, type GameInfo, type GameInfoEvent, type GameState, type GameStateUpdatedEvent, type HapticFeedbackEvent, type HapticFeedbackType, type IncomingGameEvent, type InventoryItem, type LevelAttempt, type LevelAttemptStars, type LevelProgressState, type LevelStars, type MultiplayerCreateRoomEvent, type MultiplayerErrorEvent, type MultiplayerGameOverEvent, type MultiplayerIceServersEvent, type MultiplayerJoinRoomEvent, type MultiplayerLeaveRoomEvent, type MultiplayerPeersEvent, type MultiplayerRefreshIceEvent, type MultiplayerRequestInviteEvent, type MultiplayerSaveGameStateEvent, type MultiplayerSessionEndedEvent, type MultiplayerSessionEvent, type MultiplayerSignalEvent, type MultiplayerSignalsEvent, type OutgoingGameEvent, type PlayAgainEvent, type Player, type PurchaseCompleteEvent, type PurchaseEvent, type ReadyEvent, type RealtimeEndReason, type RealtimeErrorCode, type RealtimeNamespace, type RealtimePeer, type RealtimeRoom, RealtimeRoomError, type RealtimeSendOptions, type RealtimeSignalKind, type RefuteGameStateEvent, RemixSDK, type SafeAreaInset, type SaveGameStateEvent, type ShopItem, type SinglePlayerGameOverEvent, type ToggleMuteEvent, type ViewContext, ZERO_SAFE_AREA_INSET, sdk };