@dreamboard-games/cli 0.1.30-alpha.0 → 0.1.30-alpha.10

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.
Files changed (156) hide show
  1. package/README.md +179 -22
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +31 -30
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -0
  4. package/dist/agent-verifier/{chunk-2NZNKIND.mjs → chunk-3IJBOLGT.mjs} +4 -12
  5. package/dist/agent-verifier/chunk-3IJBOLGT.mjs.map +1 -0
  6. package/dist/agent-verifier/{chunk-6A5HRJMQ.mjs → chunk-4GU3PCHV.mjs} +62 -99
  7. package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +1 -0
  8. package/dist/agent-verifier/{chunk-SYPLYRGB.mjs → chunk-6XRC5PWB.mjs} +119 -310
  9. package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +1 -0
  10. package/dist/agent-verifier/{chunk-BVVNBJM4.mjs → chunk-COB56ESI.mjs} +2 -1
  11. package/dist/agent-verifier/chunk-COB56ESI.mjs.map +1 -0
  12. package/dist/agent-verifier/{chunk-2GBBP27W.mjs → chunk-F2DIOJJZ.mjs} +1 -0
  13. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +1 -0
  14. package/dist/agent-verifier/{chunk-CFU5EWIC.mjs → chunk-G42BGGG2.mjs} +7 -6
  15. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +1 -0
  16. package/dist/agent-verifier/{chunk-XYDL7GY6.mjs → chunk-H6XDQJ3N.mjs} +1 -0
  17. package/dist/agent-verifier/{chunk-LM3OZLZG.mjs → chunk-IAYRNVUC.mjs} +1 -0
  18. package/dist/agent-verifier/chunk-IAYRNVUC.mjs.map +1 -0
  19. package/dist/agent-verifier/{chunk-2QMNAVV4.mjs → chunk-JZTH3EMV.mjs} +2 -1
  20. package/dist/agent-verifier/chunk-JZTH3EMV.mjs.map +1 -0
  21. package/dist/agent-verifier/chunk-KK47X7RV.mjs +14 -0
  22. package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +1 -0
  23. package/dist/agent-verifier/{chunk-SHUMAVAP.mjs → chunk-M7UVBANQ.mjs} +8 -9
  24. package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
  25. package/dist/agent-verifier/{chunk-RJBLBYHX.mjs → chunk-MGXX4WFR.mjs} +87 -22
  26. package/dist/agent-verifier/chunk-MGXX4WFR.mjs.map +1 -0
  27. package/dist/agent-verifier/{chunk-2E5P5NWG.mjs → chunk-NAK77WXW.mjs} +58 -126
  28. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +1 -0
  29. package/dist/agent-verifier/{chunk-CEQ2VJWN.mjs → chunk-POBFNXD4.mjs} +2 -1
  30. package/dist/agent-verifier/chunk-POBFNXD4.mjs.map +1 -0
  31. package/dist/agent-verifier/{chunk-6UUJEYDV.mjs → chunk-QBAF7EYR.mjs} +1 -0
  32. package/dist/agent-verifier/chunk-QBAF7EYR.mjs.map +1 -0
  33. package/dist/agent-verifier/{chunk-7653FPGJ.mjs → chunk-RHI6S4SU.mjs} +3 -2
  34. package/dist/agent-verifier/chunk-RHI6S4SU.mjs.map +1 -0
  35. package/dist/agent-verifier/{chunk-MINCYHXN.mjs → chunk-TAEQKBJB.mjs} +1 -0
  36. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +1 -0
  37. package/dist/agent-verifier/{chunk-PM3SVG6R.mjs → chunk-TLYGTHXU.mjs} +3 -2
  38. package/dist/agent-verifier/chunk-TLYGTHXU.mjs.map +1 -0
  39. package/dist/agent-verifier/{chunk-EIQWDQWJ.mjs → chunk-UWJIZML3.mjs} +13 -14
  40. package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +1 -0
  41. package/dist/agent-verifier/{chunk-DTMJCPS4.mjs → chunk-VLOIZDR6.mjs} +15 -31
  42. package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +1 -0
  43. package/dist/agent-verifier/{chunk-HJFQDSTU.mjs → chunk-W2MDP5ZN.mjs} +6 -5
  44. package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +1 -0
  45. package/dist/agent-verifier/{chunk-CEDUHGNH.mjs → chunk-XKCJBIRY.mjs} +2 -1
  46. package/dist/agent-verifier/chunk-XKCJBIRY.mjs.map +1 -0
  47. package/dist/agent-verifier/{chunk-VYJTHSYR.mjs → chunk-YDIOW2BO.mjs} +2 -1
  48. package/dist/agent-verifier/chunk-YDIOW2BO.mjs.map +1 -0
  49. package/dist/agent-verifier/{chunk-MRCUP5SW.mjs → chunk-YE7UAO3T.mjs} +1 -0
  50. package/dist/agent-verifier/chunk-YE7UAO3T.mjs.map +1 -0
  51. package/dist/agent-verifier/{chunk-EOQIV6PS.mjs → chunk-YR664DJX.mjs} +111 -116
  52. package/dist/agent-verifier/chunk-YR664DJX.mjs.map +1 -0
  53. package/dist/agent-verifier/{chunk-2SZHMP6F.mjs → chunk-Z6OZWUIZ.mjs} +6 -9
  54. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +1 -0
  55. package/dist/agent-verifier/{chunk-RBDDIIPM.mjs → chunk-ZEELHSY3.mjs} +1 -0
  56. package/dist/agent-verifier/chunk-ZEELHSY3.mjs.map +1 -0
  57. package/dist/agent-verifier/{compile-WNCQQVOF.mjs → compile-C2VIP6VC.mjs} +27 -27
  58. package/dist/agent-verifier/compile-C2VIP6VC.mjs.map +1 -0
  59. package/dist/agent-verifier/{global-config-WX3ZZIVU.mjs → global-config-XHL7BCKN.mjs} +6 -5
  60. package/dist/agent-verifier/global-config-XHL7BCKN.mjs.map +1 -0
  61. package/dist/agent-verifier/{keychain-backend-TNOPQV3Z.mjs → keychain-backend-A3MRWLPF.mjs} +2 -1
  62. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +1 -0
  63. package/dist/agent-verifier/{local-files-MTPLP62S.mjs → local-files-ZW52HSVT.mjs} +10 -11
  64. package/dist/agent-verifier/local-files-ZW52HSVT.mjs.map +1 -0
  65. package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs +10 -0
  66. package/dist/agent-verifier/local-typecheck-3JXL2NMG.mjs.map +1 -0
  67. package/dist/agent-verifier/{materialize-workspace-EWGZIVOY.mjs → materialize-workspace-BKZLLFI4.mjs} +20 -20
  68. package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +1 -0
  69. package/dist/agent-verifier/{project-state-7GR6BQTQ.mjs → project-state-XKUSCFSV.mjs} +3 -2
  70. package/dist/agent-verifier/project-state-XKUSCFSV.mjs.map +1 -0
  71. package/dist/agent-verifier/{prompt-3BAINGAQ.mjs → prompt-VKHMCQT6.mjs} +2 -1
  72. package/dist/agent-verifier/prompt-VKHMCQT6.mjs.map +1 -0
  73. package/dist/agent-verifier/{reducer-bundle-preflight-C73LEXI2.mjs → reducer-bundle-preflight-7NYZF5ZT.mjs} +6 -9
  74. package/dist/agent-verifier/reducer-bundle-preflight-7NYZF5ZT.mjs.map +1 -0
  75. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +11 -0
  76. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs.map +1 -0
  77. package/dist/agent-verifier/{reducer-native-test-harness-GMWBUISX.mjs → reducer-native-test-harness-D4VWPIAC.mjs} +14 -17
  78. package/dist/agent-verifier/reducer-native-test-harness-D4VWPIAC.mjs.map +1 -0
  79. package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs +26 -0
  80. package/dist/agent-verifier/static-scaffold-JCRBDKEH.mjs.map +1 -0
  81. package/dist/agent-verifier/{sync-LOQAH4RC.mjs → sync-UTL2IIZV.mjs} +35 -39
  82. package/dist/agent-verifier/sync-UTL2IIZV.mjs.map +1 -0
  83. package/dist/agent-verifier/{test-YOJERVHN.mjs → test-H26XCBFA.mjs} +29 -31
  84. package/dist/agent-verifier/test-H26XCBFA.mjs.map +1 -0
  85. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs +10 -0
  86. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs.map +1 -0
  87. package/dist/agent-verifier/{workspace-dependencies-HZ6VVS4G.mjs → workspace-dependencies-ULZZZPNX.mjs} +5 -4
  88. package/dist/agent-verifier/workspace-dependencies-ULZZZPNX.mjs.map +1 -0
  89. package/dist/{chunk-TSJVWTJO.js → chunk-GXM7RRZJ.js} +14 -11
  90. package/dist/chunk-GXM7RRZJ.js.map +1 -0
  91. package/dist/{chunk-3XNJT3RK.js → chunk-P5TITCD3.js} +808 -17878
  92. package/dist/chunk-P5TITCD3.js.map +1 -0
  93. package/dist/{global-config-UKSWNDTX.js → global-config-WPJRXVDO.js} +2 -2
  94. package/dist/global-config-WPJRXVDO.js.map +1 -0
  95. package/dist/index.js +987 -255
  96. package/dist/index.js.map +1 -1
  97. package/dist/internal.js +2 -3
  98. package/package.json +8 -7
  99. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  100. package/skills/dreamboard/references/cli.md +104 -0
  101. package/skills/dreamboard/references/game-interface.md +548 -0
  102. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  103. package/skills/dreamboard/references/quickstart.md +66 -0
  104. package/skills/dreamboard/references/reducer.md +864 -0
  105. package/skills/dreamboard/references/rule-authoring.md +147 -0
  106. package/skills/dreamboard/references/testing.md +249 -0
  107. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  108. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  109. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  110. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  111. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  112. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  113. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  114. package/dist/agent-verifier/static-scaffold-4YEQME5N.mjs +0 -28
  115. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  116. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  117. package/dist/chunk-3XNJT3RK.js.map +0 -1
  118. package/dist/chunk-7FOO4AJI.js +0 -50
  119. package/dist/chunk-7FOO4AJI.js.map +0 -1
  120. package/dist/chunk-TSJVWTJO.js.map +0 -1
  121. package/dist/internal.d.ts +0 -311
  122. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  123. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  124. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  125. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  126. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  127. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  128. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  129. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  130. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  131. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  132. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  133. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  134. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  135. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  136. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  137. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  138. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  139. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  140. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  141. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  142. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  143. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  144. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  145. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  146. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  147. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  148. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  149. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  150. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  151. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  152. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  153. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  154. package/dist/testing-KLSV6CPJ.js +0 -674
  155. package/dist/testing-KLSV6CPJ.js.map +0 -1
  156. /package/dist/{global-config-UKSWNDTX.js.map → agent-verifier/chunk-H6XDQJ3N.mjs.map} +0 -0
@@ -1,583 +0,0 @@
1
- import type { StoreApi } from "zustand/vanilla";
2
- import type { ValidationResult } from "@dreamboard-games/sdk/runtime/runtime-api";
3
- import type { LoggerLike } from "./logger.js";
4
- import {
5
- PERF_MARK_NAMES,
6
- correlateVersion,
7
- correlateSyncId,
8
- findActionIdByVersion,
9
- recordMark,
10
- } from "./perf.js";
11
- import type { HostSessionTransport } from "./host-session-transport.js";
12
- import type {
13
- HostActionSubmitWireResponse,
14
- NormalizedSubmitResponse,
15
- } from "./session-ingress.js";
16
- import {
17
- normalizeEvent,
18
- normalizeSnapshot,
19
- normalizeSubmitResponse,
20
- } from "./session-ingress.js";
21
- import { getSessionRecoveryDetails } from "./session-recovery.js";
22
- import {
23
- getGameplayViewport,
24
- getSessionContext,
25
- resolveControllablePlayerIds,
26
- } from "./session-model.js";
27
- import type { SSEManagerLike } from "./session-live-runtime.js";
28
- import { createSessionLiveRuntime } from "./session-live-runtime.js";
29
- import {
30
- createInitialUnifiedSessionState,
31
- reduceSessionState,
32
- type SessionStateEffect,
33
- type SessionStateIngress,
34
- type SessionStateReducerEnvironment,
35
- type UnifiedSessionState,
36
- } from "./session-state-reducer.js";
37
-
38
- export interface SessionIngressControllerActions {
39
- loadSessionByShortCode: (input: {
40
- shortCode: string;
41
- userId?: string | null;
42
- requestedPlayerId?: string | null;
43
- source?: string;
44
- }) => Promise<void>;
45
- loadSessionSnapshot: (input: {
46
- sessionId: string;
47
- userId?: string | null;
48
- requestedPlayerId?: string | null;
49
- expectedPerspectivePlayerId?: string | null;
50
- source?: string;
51
- }) => Promise<void>;
52
- startSession: (input: {
53
- sessionId: string;
54
- userId?: string | null;
55
- source?: string;
56
- }) => Promise<void>;
57
- submitInteraction: (input: {
58
- sessionId: string;
59
- playerId: string;
60
- interactionId: string;
61
- params: unknown;
62
- clientActionId?: string | null;
63
- }) => Promise<void>;
64
- validateInteraction: (input: {
65
- sessionId: string;
66
- playerId: string;
67
- interactionId: string;
68
- params: unknown;
69
- }) => Promise<ValidationResult>;
70
- restoreHistory: (input: {
71
- sessionId: string;
72
- entryId: string;
73
- }) => Promise<void>;
74
- selectPlayer: (playerId: string) => void;
75
- clearConnectionError: () => void;
76
- enqueueActionRejected: (reason: string, targetPlayer?: string) => void;
77
- closeStreams: () => void;
78
- onStateAck: (syncId: number) => void;
79
- markNotificationRead: (id: string) => void;
80
- clearNotifications: () => void;
81
- dismissHostFeedback: (id: string) => void;
82
- clearHostFeedback: () => void;
83
- clearSSEEvents: () => void;
84
- reset: () => void;
85
- }
86
-
87
- export interface CreateSessionIngressControllerOptions<
88
- TStore extends UnifiedSessionState,
89
- > {
90
- store: StoreApi<TStore>;
91
- createSseManager: () => SSEManagerLike;
92
- transport: HostSessionTransport;
93
- logger: LoggerLike;
94
- fallbackToAllSeatsWhenUserIdMissing: boolean;
95
- reducerEnvironment: SessionStateReducerEnvironment;
96
- }
97
-
98
- function describeSseError(error: unknown): string {
99
- if (error instanceof Error) {
100
- if (
101
- error.message.includes("429") ||
102
- error.message.includes("400 Bad Request")
103
- ) {
104
- return "Too many live views are open for this session. Close unused tabs or devices, then refresh.";
105
- }
106
- return "Live session updates disconnected. Check your network and refresh to reconnect.";
107
- }
108
- return "Live session updates disconnected. Refresh to reconnect.";
109
- }
110
-
111
- function describeCommandFailure(error: unknown, fallback: string): string {
112
- if (error instanceof Error && error.message.trim()) {
113
- return error.message;
114
- }
115
- const payload = error as { detail?: unknown; message?: unknown } | null;
116
- if (typeof payload?.detail === "string" && payload.detail.trim()) {
117
- return payload.detail;
118
- }
119
- if (typeof payload?.message === "string" && payload.message.trim()) {
120
- return payload.message;
121
- }
122
- return fallback;
123
- }
124
-
125
- function createSubmissionError(
126
- errorCode: string | undefined,
127
- message: string | undefined,
128
- ): Error & { errorCode?: string } {
129
- const error = new Error(message ?? "Interaction rejected") as Error & {
130
- errorCode?: string;
131
- };
132
- error.name = "SubmissionError";
133
- error.errorCode = errorCode;
134
- return error;
135
- }
136
-
137
- function sleep(ms: number): Promise<void> {
138
- return new Promise((resolve) => setTimeout(resolve, ms));
139
- }
140
-
141
- export function createSessionIngressController<
142
- TStore extends UnifiedSessionState,
143
- >(
144
- options: CreateSessionIngressControllerOptions<TStore>,
145
- ): SessionIngressControllerActions {
146
- const {
147
- store,
148
- transport,
149
- logger,
150
- fallbackToAllSeatsWhenUserIdMissing,
151
- reducerEnvironment,
152
- } = options;
153
-
154
- const dispatchIngress = (ingress: SessionStateIngress) => {
155
- const { state, effects } = reduceSessionState(
156
- store.getState(),
157
- ingress,
158
- reducerEnvironment,
159
- );
160
- store.setState(state as Partial<TStore>);
161
- effects.forEach(executeEffect);
162
- };
163
-
164
- const runtime = createSessionLiveRuntime({
165
- createSseManager: options.createSseManager,
166
- onConnectionChange: (channel, connected) => {
167
- dispatchIngress({ type: "connection.changed", channel, connected });
168
- },
169
- onConnectionError: (error) => {
170
- logger.error("[UnifiedSession] SSE connection error:", error);
171
- dispatchIngress({
172
- type: "connection.failed",
173
- message: describeSseError(error),
174
- });
175
- },
176
- onConnectionRecovering: (details) => {
177
- dispatchIngress({
178
- type: "connection.recovering",
179
- message: details.message,
180
- retryAfterMs: details.retryAfterMs,
181
- attempt: details.attempt,
182
- });
183
- },
184
- onLiveMessage: (message) => {
185
- const state = store.getState();
186
- const context = getSessionContext(state.session);
187
- const event = normalizeEvent(message, {
188
- currentContext: context,
189
- currentGameplay: getGameplayViewport(state.session),
190
- userId: context?.userId ?? null,
191
- });
192
- dispatchIngress({
193
- type: "event.received",
194
- source: "sse",
195
- event,
196
- debugEvent: { eventType: message.type, data: message },
197
- clientActionId:
198
- message.type === "session.gameplayUpdated"
199
- ? message.causation?.clientActionId
200
- : undefined,
201
- });
202
- },
203
- });
204
-
205
- function executeEffect(effect: SessionStateEffect): void {
206
- switch (effect.type) {
207
- case "requestGameplayResync":
208
- logger.warn(
209
- `[UnifiedSession] ${effect.reason}; reconnecting to refresh gameplay.bootstrap`,
210
- );
211
- runtime.requestGameplayResync(
212
- effect.sessionId,
213
- effect.playerId,
214
- "board-static-resync",
215
- );
216
- break;
217
- case "reconnectGameplay":
218
- runtime.reconnectGameplay(
219
- effect.sessionId,
220
- effect.playerId,
221
- effect.source,
222
- );
223
- break;
224
- case "perf.storeApplied": {
225
- const actionId = findActionIdByVersion(effect.gameplayVersion);
226
- if (!actionId) break;
227
- recordMark(actionId, PERF_MARK_NAMES.T5_STORE_APPLIED, {
228
- extra: {
229
- version: effect.gameplayVersion,
230
- syncId: effect.syncId,
231
- },
232
- });
233
- correlateSyncId(actionId, effect.syncId);
234
- break;
235
- }
236
- case "log.warn":
237
- logger.warn(effect.message);
238
- break;
239
- }
240
- }
241
-
242
- function connectLiveSession(
243
- sessionId: string,
244
- userId: string | null,
245
- connectOptions: { source?: string; playerId?: string } = {},
246
- ) {
247
- const context = getSessionContext(store.getState().session);
248
- const controllablePlayerIds = context
249
- ? resolveControllablePlayerIds(
250
- context.switchablePlayerIds,
251
- context.seats,
252
- userId,
253
- fallbackToAllSeatsWhenUserIdMissing,
254
- )
255
- : [];
256
- const session = store.getState().session;
257
- const playerId =
258
- connectOptions.playerId ??
259
- (session.type === "gameplay"
260
- ? session.perspective.playerId
261
- : controllablePlayerIds[0]);
262
- dispatchIngress({
263
- type: "connection.prepared",
264
- userId,
265
- switchablePlayerIds: controllablePlayerIds,
266
- });
267
- logger.log(`[UnifiedSession] Connecting to session: ${sessionId}`);
268
- runtime.connectSession(sessionId, {
269
- source: connectOptions.source,
270
- playerId,
271
- });
272
- }
273
-
274
- async function runSnapshotCommand(
275
- input: {
276
- target?: { sessionId?: string; shortCode?: string };
277
- sourceLabel: "short-code" | "snapshot" | "start" | "dev-new";
278
- connectSource?: string;
279
- userId?: string | null;
280
- expectedPerspectivePlayerId?: string | null;
281
- preserveCurrentOnFailure?: boolean;
282
- load: () => Promise<
283
- Awaited<ReturnType<HostSessionTransport["loadSessionSnapshot"]>>
284
- >;
285
- },
286
- failureMessage: string,
287
- ): Promise<void> {
288
- if (!input.preserveCurrentOnFailure) {
289
- dispatchIngress({ type: "command.loading", target: input.target });
290
- }
291
- let recoveryAttempt = 0;
292
- while (true) {
293
- try {
294
- const snapshot = await input.load();
295
- const normalized = normalizeSnapshot(snapshot, {
296
- userId: input.userId ?? null,
297
- previousGameplay: getGameplayViewport(store.getState().session),
298
- });
299
- dispatchIngress({
300
- type: "snapshot.loaded",
301
- source: input.sourceLabel,
302
- snapshot: normalized,
303
- expectedPerspectivePlayerId: input.expectedPerspectivePlayerId,
304
- });
305
- connectLiveSession(
306
- normalized.context.identity.sessionId,
307
- input.userId ?? null,
308
- {
309
- source: input.connectSource,
310
- playerId:
311
- normalized.type === "gameplay"
312
- ? normalized.perspective.playerId
313
- : undefined,
314
- },
315
- );
316
- return;
317
- } catch (error) {
318
- const recovery = getSessionRecoveryDetails(error);
319
- if (recovery && recoveryAttempt < 60) {
320
- recoveryAttempt += 1;
321
- dispatchIngress({
322
- type: "connection.recovering",
323
- message: recovery.message,
324
- retryAfterMs: recovery.retryAfterMs,
325
- attempt: recoveryAttempt,
326
- });
327
- await sleep(recovery.retryAfterMs);
328
- continue;
329
- }
330
- if (!input.preserveCurrentOnFailure) {
331
- dispatchIngress({
332
- type: "command.failed",
333
- message: describeCommandFailure(error, failureMessage),
334
- });
335
- }
336
- throw error;
337
- }
338
- }
339
- }
340
-
341
- function normalizeSubmitResponseForCurrentState(
342
- response: HostActionSubmitWireResponse,
343
- ): NormalizedSubmitResponse {
344
- const state = store.getState();
345
- const context = getSessionContext(state.session);
346
- return normalizeSubmitResponse(response, {
347
- currentContext: context,
348
- currentGameplay: getGameplayViewport(state.session),
349
- userId: context?.userId ?? null,
350
- });
351
- }
352
-
353
- function dispatchSubmitResponse(
354
- event: NormalizedSubmitResponse,
355
- clientActionId?: string | null,
356
- ) {
357
- if (!event) return;
358
- if (clientActionId && event.type === "session.gameplayUpdated") {
359
- recordMark(clientActionId, PERF_MARK_NAMES.T3B_RESPONSE_APPLIED, {
360
- extra: { version: event.gameplay.version },
361
- });
362
- }
363
- dispatchIngress({
364
- type: "event.received",
365
- source: "submit-response",
366
- event,
367
- clientActionId,
368
- });
369
- }
370
-
371
- return {
372
- loadSessionByShortCode: async (input) => {
373
- await runSnapshotCommand(
374
- {
375
- target: { shortCode: input.shortCode },
376
- sourceLabel: "short-code",
377
- connectSource: input.source,
378
- userId: input.userId,
379
- load: () =>
380
- transport.loadSessionByShortCode({
381
- shortCode: input.shortCode,
382
- requestedPlayerId: input.requestedPlayerId,
383
- }),
384
- },
385
- "Failed to load session by short code.",
386
- );
387
- },
388
- loadSessionSnapshot: async (input) => {
389
- await runSnapshotCommand(
390
- {
391
- target: { sessionId: input.sessionId },
392
- sourceLabel: input.source === "dev-new" ? "dev-new" : "snapshot",
393
- connectSource: input.source,
394
- userId: input.userId,
395
- expectedPerspectivePlayerId: input.expectedPerspectivePlayerId,
396
- preserveCurrentOnFailure: input.source === "player-switch",
397
- load: () =>
398
- transport.loadSessionSnapshot({
399
- sessionId: input.sessionId,
400
- requestedPlayerId: input.requestedPlayerId,
401
- }),
402
- },
403
- "Failed to load session snapshot.",
404
- );
405
- },
406
- startSession: async (input) => {
407
- await runSnapshotCommand(
408
- {
409
- target: { sessionId: input.sessionId },
410
- sourceLabel: "start",
411
- connectSource: input.source,
412
- userId: input.userId,
413
- load: () => transport.startSession({ sessionId: input.sessionId }),
414
- },
415
- "Failed to start session.",
416
- );
417
- },
418
- submitInteraction: async (input) => {
419
- const gameplay = getGameplayViewport(store.getState().session);
420
- if (!gameplay) {
421
- throw new Error("No renderable gameplay snapshot is available.");
422
- }
423
- if (input.clientActionId) {
424
- recordMark(input.clientActionId, PERF_MARK_NAMES.T2_HTTP_SENT, {
425
- extra: {
426
- playerId: input.playerId,
427
- interactionId: input.interactionId,
428
- expectedVersion: gameplay.version,
429
- },
430
- });
431
- }
432
- let response: HostActionSubmitWireResponse | null = null;
433
- let recoveryAttempt = 0;
434
- while (!response) {
435
- try {
436
- response = await transport.submitInteraction({
437
- sessionId: input.sessionId,
438
- playerId: input.playerId,
439
- interactionId: input.interactionId,
440
- expectedVersion: gameplay.version,
441
- actionSetVersion: gameplay.actionSetVersion,
442
- params: input.params,
443
- clientActionId: input.clientActionId,
444
- });
445
- } catch (error) {
446
- const recovery = getSessionRecoveryDetails(error);
447
- if (recovery && recoveryAttempt < 60) {
448
- recoveryAttempt += 1;
449
- dispatchIngress({
450
- type: "connection.recovering",
451
- message: recovery.message,
452
- retryAfterMs: recovery.retryAfterMs,
453
- attempt: recoveryAttempt,
454
- });
455
- await sleep(recovery.retryAfterMs);
456
- continue;
457
- }
458
- const message = describeCommandFailure(error, "Interaction rejected");
459
- dispatchIngress({
460
- type: "feedback.actionRejected",
461
- reason: message,
462
- targetPlayer: input.playerId,
463
- });
464
- throw createSubmissionError("api-error", message);
465
- }
466
- }
467
- const normalizedResponse =
468
- normalizeSubmitResponseForCurrentState(response);
469
- if (input.clientActionId) {
470
- recordMark(input.clientActionId, PERF_MARK_NAMES.T3_HTTP_RESPONSE, {
471
- extra: {
472
- accepted: response.accepted,
473
- errorCode: response.errorCode,
474
- version: response.version,
475
- transport: "ok",
476
- },
477
- });
478
- const acceptedGameplayVersion =
479
- normalizedResponse?.type === "session.gameplayUpdated"
480
- ? normalizedResponse.gameplay.version
481
- : response.version;
482
- if (
483
- acceptedGameplayVersion !== undefined &&
484
- response.accepted !== false
485
- ) {
486
- correlateVersion(input.clientActionId, acceptedGameplayVersion);
487
- }
488
- }
489
- if (response.accepted !== false) {
490
- dispatchSubmitResponse(normalizedResponse, input.clientActionId);
491
- return;
492
- }
493
- dispatchIngress({
494
- type: "feedback.actionRejected",
495
- reason: response.message ?? "Interaction rejected",
496
- targetPlayer: input.playerId,
497
- });
498
- throw createSubmissionError(
499
- response.errorCode ?? undefined,
500
- response.message ?? "Interaction rejected",
501
- );
502
- },
503
- validateInteraction: async (input) => {
504
- const gameplay = getGameplayViewport(store.getState().session);
505
- if (!gameplay) {
506
- return {
507
- valid: false,
508
- errorCode: "runtime-unavailable",
509
- message: "No renderable gameplay snapshot is available.",
510
- };
511
- }
512
- return transport.validateInteraction({
513
- sessionId: input.sessionId,
514
- playerId: input.playerId,
515
- interactionId: input.interactionId,
516
- expectedVersion: gameplay.version,
517
- actionSetVersion: gameplay.actionSetVersion,
518
- params: input.params,
519
- });
520
- },
521
- restoreHistory: async (input) => {
522
- const event = await transport.restoreHistory(input);
523
- if (event) {
524
- dispatchIngress({
525
- type: "event.received",
526
- source: "submit-response",
527
- event: normalizeEvent(event, {
528
- currentContext: getSessionContext(store.getState().session),
529
- currentGameplay: getGameplayViewport(store.getState().session),
530
- userId: getSessionContext(store.getState().session)?.userId ?? null,
531
- }),
532
- debugEvent: { eventType: event.type, data: event },
533
- });
534
- }
535
- },
536
- selectPlayer: (playerId) => {
537
- const session = store.getState().session;
538
- const currentPlayerId =
539
- session.type === "gameplay"
540
- ? session.perspective.playerId
541
- : session.type === "gameplayLoading"
542
- ? session.requestedPlayerId
543
- : null;
544
- if (playerId === currentPlayerId) {
545
- return;
546
- }
547
- const context = getSessionContext(session);
548
- if (!context) return;
549
- dispatchIngress({
550
- type: "local.playerSelected",
551
- playerId,
552
- sessionId:
553
- runtime.getConnectedSessionId() ?? context.identity.sessionId,
554
- });
555
- },
556
- clearConnectionError: () =>
557
- dispatchIngress({ type: "connection.errorCleared" }),
558
- enqueueActionRejected: (reason, targetPlayer) =>
559
- dispatchIngress({
560
- type: "feedback.actionRejected",
561
- reason,
562
- targetPlayer,
563
- }),
564
- closeStreams: () => {
565
- runtime.closeStreams();
566
- dispatchIngress({ type: "streams.closed" });
567
- },
568
- onStateAck: () => dispatchIngress({ type: "activity.stateAcked" }),
569
- markNotificationRead: (id) =>
570
- dispatchIngress({ type: "activity.notificationRead", id }),
571
- clearNotifications: () =>
572
- dispatchIngress({ type: "activity.notificationsCleared" }),
573
- dismissHostFeedback: (id) =>
574
- dispatchIngress({ type: "activity.hostFeedbackDismissed", id }),
575
- clearHostFeedback: () =>
576
- dispatchIngress({ type: "activity.hostFeedbackCleared" }),
577
- clearSSEEvents: () => dispatchIngress({ type: "debug.sseEventsCleared" }),
578
- reset: () => {
579
- runtime.closeStreams();
580
- store.setState(createInitialUnifiedSessionState() as Partial<TStore>);
581
- },
582
- };
583
- }