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

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 (114) hide show
  1. package/README.md +179 -22
  2. package/dist/{chunk-TSJVWTJO.js → chunk-N7XPNNUI.js} +14 -12
  3. package/dist/chunk-N7XPNNUI.js.map +1 -0
  4. package/dist/chunk-SEGVTWSK.js +44 -0
  5. package/dist/{chunk-3XNJT3RK.js → chunk-TAQKH67O.js} +21279 -35845
  6. package/dist/chunk-TAQKH67O.js.map +1 -0
  7. package/dist/{global-config-UKSWNDTX.js → global-config-S4ZIPECE.js} +3 -3
  8. package/dist/index.js +955 -230
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal.js +3 -4
  11. package/dist/{agent-verifier/keychain-backend-TNOPQV3Z.mjs → keychain-backend-HDF4TZDL.js} +2 -1
  12. package/dist/{agent-verifier/prompt-3BAINGAQ.mjs → prompt-NDV3AE5L.js} +2 -1
  13. package/package.json +6 -6
  14. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  15. package/skills/dreamboard/references/cli.md +104 -0
  16. package/skills/dreamboard/references/game-interface.md +548 -0
  17. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  18. package/skills/dreamboard/references/quickstart.md +66 -0
  19. package/skills/dreamboard/references/reducer.md +864 -0
  20. package/skills/dreamboard/references/rule-authoring.md +147 -0
  21. package/skills/dreamboard/references/testing.md +249 -0
  22. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  23. package/dist/agent-verifier/agent-workspace-verifier.mjs +0 -227
  24. package/dist/agent-verifier/chunk-2E5P5NWG.mjs +0 -835
  25. package/dist/agent-verifier/chunk-2GBBP27W.mjs +0 -301
  26. package/dist/agent-verifier/chunk-2NZNKIND.mjs +0 -166
  27. package/dist/agent-verifier/chunk-2QMNAVV4.mjs +0 -14522
  28. package/dist/agent-verifier/chunk-2SZHMP6F.mjs +0 -264
  29. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  30. package/dist/agent-verifier/chunk-6A5HRJMQ.mjs +0 -3174
  31. package/dist/agent-verifier/chunk-6UUJEYDV.mjs +0 -213
  32. package/dist/agent-verifier/chunk-7653FPGJ.mjs +0 -381
  33. package/dist/agent-verifier/chunk-BVVNBJM4.mjs +0 -221
  34. package/dist/agent-verifier/chunk-CEDUHGNH.mjs +0 -74
  35. package/dist/agent-verifier/chunk-CEQ2VJWN.mjs +0 -149
  36. package/dist/agent-verifier/chunk-CFU5EWIC.mjs +0 -69
  37. package/dist/agent-verifier/chunk-DTMJCPS4.mjs +0 -730
  38. package/dist/agent-verifier/chunk-EIQWDQWJ.mjs +0 -186
  39. package/dist/agent-verifier/chunk-EOQIV6PS.mjs +0 -649
  40. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  41. package/dist/agent-verifier/chunk-HJFQDSTU.mjs +0 -225
  42. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  43. package/dist/agent-verifier/chunk-LM3OZLZG.mjs +0 -48
  44. package/dist/agent-verifier/chunk-MINCYHXN.mjs +0 -106
  45. package/dist/agent-verifier/chunk-MRCUP5SW.mjs +0 -128
  46. package/dist/agent-verifier/chunk-PM3SVG6R.mjs +0 -38
  47. package/dist/agent-verifier/chunk-RBDDIIPM.mjs +0 -19
  48. package/dist/agent-verifier/chunk-RJBLBYHX.mjs +0 -1681
  49. package/dist/agent-verifier/chunk-SHUMAVAP.mjs +0 -59
  50. package/dist/agent-verifier/chunk-SYPLYRGB.mjs +0 -2812
  51. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  52. package/dist/agent-verifier/chunk-VYJTHSYR.mjs +0 -44
  53. package/dist/agent-verifier/chunk-XYDL7GY6.mjs +0 -10
  54. package/dist/agent-verifier/compile-WNCQQVOF.mjs +0 -313
  55. package/dist/agent-verifier/global-config-WX3ZZIVU.mjs +0 -17
  56. package/dist/agent-verifier/local-files-MTPLP62S.mjs +0 -46
  57. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  58. package/dist/agent-verifier/materialize-workspace-EWGZIVOY.mjs +0 -90
  59. package/dist/agent-verifier/project-state-7GR6BQTQ.mjs +0 -32
  60. package/dist/agent-verifier/reducer-bundle-preflight-C73LEXI2.mjs +0 -23
  61. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  62. package/dist/agent-verifier/reducer-native-test-harness-GMWBUISX.mjs +0 -53
  63. package/dist/agent-verifier/static-scaffold-4YEQME5N.mjs +0 -28
  64. package/dist/agent-verifier/sync-LOQAH4RC.mjs +0 -594
  65. package/dist/agent-verifier/test-YOJERVHN.mjs +0 -356
  66. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  67. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  68. package/dist/agent-verifier/workspace-dependencies-HZ6VVS4G.mjs +0 -14
  69. package/dist/chunk-2H7UOFLK.js +0 -11
  70. package/dist/chunk-3XNJT3RK.js.map +0 -1
  71. package/dist/chunk-7FOO4AJI.js +0 -50
  72. package/dist/chunk-7FOO4AJI.js.map +0 -1
  73. package/dist/chunk-TSJVWTJO.js.map +0 -1
  74. package/dist/internal.d.ts +0 -311
  75. package/dist/keychain-backend-JHTXAKWC.js +0 -135
  76. package/dist/prompt-GMZABCJC.js +0 -756
  77. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  78. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  79. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  80. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  81. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  82. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  83. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  84. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  85. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  86. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  87. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  88. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  89. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  90. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  91. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  92. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  93. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  94. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  95. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  96. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  97. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  98. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  99. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  100. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  101. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  102. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  103. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  104. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  105. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  106. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  107. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  108. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  109. package/dist/testing-KLSV6CPJ.js +0 -674
  110. package/dist/testing-KLSV6CPJ.js.map +0 -1
  111. /package/dist/{chunk-2H7UOFLK.js.map → chunk-SEGVTWSK.js.map} +0 -0
  112. /package/dist/{global-config-UKSWNDTX.js.map → global-config-S4ZIPECE.js.map} +0 -0
  113. /package/dist/{keychain-backend-JHTXAKWC.js.map → keychain-backend-HDF4TZDL.js.map} +0 -0
  114. /package/dist/{prompt-GMZABCJC.js.map → prompt-NDV3AE5L.js.map} +0 -0
@@ -1,551 +0,0 @@
1
- import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
2
- import type { ValidationResult } from "@dreamboard-games/sdk/runtime/runtime-api";
3
- import { PluginBridge } from "./plugin-bridge.js";
4
- import { PluginHealthCheck } from "./plugin-health-check.js";
5
- import type { LoggerLike } from "./logger.js";
6
- import { consoleLogger } from "./logger.js";
7
- import { PERF_MARK_NAMES, findActionIdBySyncId, recordMark } from "./perf.js";
8
-
9
- export interface GameSessionStoreApi {
10
- getStateSnapshot: () => PluginStateSnapshot;
11
- subscribe: (callback: () => void) => () => void;
12
- onStateAck: (syncId: number) => void;
13
- markNotificationRead: (notificationId: string) => void;
14
- }
15
-
16
- export type GatewayState = "loading" | "handshaking" | "connected" | "error";
17
-
18
- /**
19
- * Per-interaction metadata the gateway forwards from the plugin down to
20
- * `onInteraction`. Currently carries just the client-minted correlation id
21
- * used by Tier-0 input-latency observability; keeping it as a bag lets
22
- * future perf/debug fields (e.g. plugin-side timestamps) ride along
23
- * without another signature change.
24
- */
25
- export interface InteractionMeta {
26
- clientActionId?: string;
27
- }
28
-
29
- export interface PluginSessionGatewayConfig {
30
- iframe: HTMLIFrameElement;
31
- sessionId: string;
32
- controllablePlayerIds: string[];
33
- controllingPlayerId: string;
34
- userId: string | null;
35
- onReady: () => void;
36
- onError: (error: Error) => void;
37
- onInteraction: (
38
- playerId: string,
39
- interactionId: string,
40
- params: unknown,
41
- meta?: InteractionMeta,
42
- ) => void | Promise<void>;
43
- onValidateInteraction: (
44
- playerId: string,
45
- interactionId: string,
46
- params: unknown,
47
- ) => Promise<ValidationResult>;
48
- onSwitchPlayer?: (playerId: string) => void;
49
- onRestoreHistory?: (entryId: string) => void;
50
- logger?: LoggerLike;
51
- }
52
-
53
- export class PluginSessionGateway {
54
- private bridge: PluginBridge | null = null;
55
- private healthCheck: PluginHealthCheck | null = null;
56
- private state: GatewayState = "loading";
57
- private unsubscribeHandlers: Array<() => void> = [];
58
- private config: PluginSessionGatewayConfig;
59
- private readyTimeout: ReturnType<typeof setTimeout> | null = null;
60
- private initStartTimeout: ReturnType<typeof setTimeout> | null = null;
61
- private initRetryInterval: ReturnType<typeof setInterval> | null = null;
62
- private storeUnsubscribe: (() => void) | null = null;
63
- private gameSessionStore: GameSessionStoreApi | null = null;
64
- private lastSentSnapshotKey: string | null = null;
65
- private logger: LoggerLike;
66
-
67
- constructor(config: PluginSessionGatewayConfig) {
68
- this.config = config;
69
- this.logger = config.logger ?? consoleLogger;
70
- }
71
-
72
- connect(): void {
73
- if (this.bridge) {
74
- this.logger.warn("[Gateway] Already connected");
75
- return;
76
- }
77
-
78
- this.state = "loading";
79
-
80
- const targetOrigin = "*";
81
- this.bridge = new PluginBridge(this.config.iframe, targetOrigin, {
82
- logger: this.logger,
83
- });
84
-
85
- this.readyTimeout = setTimeout(() => {
86
- if (this.state !== "connected") {
87
- this.handleError(
88
- new Error("Plugin failed to send ready message within 10 seconds"),
89
- );
90
- }
91
- }, 10000);
92
-
93
- this.setupInteractionHandler();
94
- this.setupValidateInteractionHandler();
95
- this.setupReadyHandler();
96
- this.setupErrorHandler();
97
- this.setupSwitchPlayerHandler();
98
- this.setupRestoreHistoryHandler();
99
- this.setupStateAckHandler();
100
- this.setupStateRenderedHandler();
101
- this.setupMarkNotificationReadHandler();
102
-
103
- this.state = "handshaking";
104
-
105
- this.initStartTimeout = setTimeout(() => {
106
- this.initStartTimeout = null;
107
- if (this.state === "error") {
108
- return;
109
- }
110
- this.sendInit();
111
- this.initRetryInterval = setInterval(() => {
112
- if (this.state === "connected") {
113
- this.clearInitRetryInterval();
114
- return;
115
- }
116
- this.sendInit();
117
- }, 250);
118
- }, 50);
119
- }
120
-
121
- attachStore(store: GameSessionStoreApi): void {
122
- if (this.state !== "connected") {
123
- this.logger.warn(
124
- "[Gateway] Cannot attach store - plugin not ready yet (state: " +
125
- this.state +
126
- ")",
127
- );
128
- return;
129
- }
130
-
131
- if (this.storeUnsubscribe) {
132
- this.logger.warn("[Gateway] Store already attached");
133
- return;
134
- }
135
-
136
- this.gameSessionStore = store;
137
-
138
- this.flushStateSync();
139
-
140
- this.storeUnsubscribe = store.subscribe(() => {
141
- this.flushStateSync();
142
- });
143
- }
144
-
145
- flushStateSync(): void {
146
- if (!this.bridge || !this.gameSessionStore) return;
147
-
148
- const snapshot = this.gameSessionStore.getStateSnapshot();
149
- const snapshotKey = this.snapshotDeliveryKey(snapshot);
150
-
151
- if (snapshotKey !== this.lastSentSnapshotKey) {
152
- this.lastSentSnapshotKey = snapshotKey;
153
- this.logger.log("[Gateway] State snapshot view present:", {
154
- syncId: snapshot.syncId,
155
- hasView: snapshot.view !== null,
156
- controllingPlayerId: snapshot.session.controllingPlayerId,
157
- controllablePlayerIds: snapshot.session.controllablePlayerIds,
158
- });
159
- this.bridge.sendStateSync(snapshot.syncId, snapshot);
160
- // Tier-0 perf: stitch the outgoing state-sync to the action
161
- // that caused it (via the syncId -> actionId map populated
162
- // at `t5_store_applied`). For non-action-driven syncs (lobby
163
- // updates etc.) the lookup returns undefined and the mark is
164
- // a no-op.
165
- const actionId = findActionIdBySyncId(snapshot.syncId);
166
- if (actionId) {
167
- recordMark(actionId, PERF_MARK_NAMES.T6_STATE_SYNC_POSTED, {
168
- extra: { syncId: snapshot.syncId },
169
- });
170
- }
171
- this.logger.log("[Gateway] Sent state-sync, syncId:", snapshot.syncId);
172
- }
173
- }
174
-
175
- getState(): GatewayState {
176
- return this.state;
177
- }
178
-
179
- disconnect(): void {
180
- if (this.readyTimeout) {
181
- clearTimeout(this.readyTimeout);
182
- this.readyTimeout = null;
183
- }
184
-
185
- if (this.initStartTimeout) {
186
- clearTimeout(this.initStartTimeout);
187
- this.initStartTimeout = null;
188
- }
189
-
190
- this.clearInitRetryInterval();
191
-
192
- if (this.healthCheck) {
193
- this.healthCheck.stop();
194
- this.healthCheck = null;
195
- }
196
-
197
- if (this.bridge) {
198
- this.bridge.disconnect();
199
- this.bridge = null;
200
- }
201
-
202
- if (this.storeUnsubscribe) {
203
- this.storeUnsubscribe();
204
- this.storeUnsubscribe = null;
205
- }
206
-
207
- this.unsubscribeHandlers.forEach((unsubscribe) => unsubscribe());
208
- this.unsubscribeHandlers = [];
209
-
210
- this.state = "loading";
211
- this.lastSentSnapshotKey = null;
212
- }
213
-
214
- private snapshotDeliveryKey(snapshot: PluginStateSnapshot): string {
215
- return JSON.stringify({
216
- syncId: snapshot.syncId,
217
- sessionId: snapshot.session.sessionId,
218
- controllingPlayerId: snapshot.session.controllingPlayerId,
219
- controllablePlayerIds: snapshot.session.controllablePlayerIds,
220
- userId: snapshot.session.userId,
221
- });
222
- }
223
-
224
- private setupReadyHandler(): void {
225
- if (!this.bridge) return;
226
-
227
- const unsubscribe = this.bridge.onPluginMessage("ready", () => {
228
- if (this.readyTimeout) {
229
- clearTimeout(this.readyTimeout);
230
- this.readyTimeout = null;
231
- }
232
- this.clearInitRetryInterval();
233
-
234
- this.state = "connected";
235
-
236
- if (this.bridge) {
237
- this.healthCheck = new PluginHealthCheck(this.bridge, {
238
- onUnhealthy: () => {
239
- this.handleError(new Error("Plugin iframe is unresponsive"));
240
- },
241
- logger: this.logger,
242
- });
243
- this.healthCheck.start();
244
- }
245
-
246
- this.config.onReady();
247
- });
248
-
249
- this.unsubscribeHandlers.push(unsubscribe);
250
- }
251
-
252
- private sendInit(): void {
253
- if (!this.bridge) {
254
- return;
255
- }
256
-
257
- this.bridge.sendInit(
258
- this.config.sessionId,
259
- this.config.controllablePlayerIds,
260
- this.config.controllingPlayerId,
261
- this.config.userId,
262
- );
263
- }
264
-
265
- private clearInitRetryInterval(): void {
266
- if (this.initRetryInterval) {
267
- clearInterval(this.initRetryInterval);
268
- this.initRetryInterval = null;
269
- }
270
- }
271
-
272
- private setupInteractionHandler(): void {
273
- if (!this.bridge) return;
274
-
275
- const unsubscribe = this.bridge.onPluginMessage(
276
- "interaction",
277
- async (message) => {
278
- // Tier-0 perf: record plugin-supplied `t0_click` and our own
279
- // `t1_host_received` against the client-minted actionId so the
280
- // rest of the pipeline (http submit, SSE, store apply) can
281
- // attach their marks to the same entry.
282
- if (message.clientActionId) {
283
- if (typeof message.clientSubmittedAtMs === "number") {
284
- recordMark(message.clientActionId, PERF_MARK_NAMES.T0_CLICK, {
285
- timestampMs: message.clientSubmittedAtMs,
286
- extra: { source: "plugin" },
287
- });
288
- }
289
- recordMark(message.clientActionId, PERF_MARK_NAMES.T1_HOST_RECEIVED, {
290
- extra: { messageId: message.messageId },
291
- });
292
- }
293
-
294
- try {
295
- await this.config.onInteraction(
296
- message.playerId,
297
- message.interactionId,
298
- message.params,
299
- message.clientActionId
300
- ? { clientActionId: message.clientActionId }
301
- : undefined,
302
- );
303
- this.sendSubmitResult(message.messageId, { accepted: true });
304
- } catch (error) {
305
- this.logger.error("[Gateway] Interaction submission error:", error);
306
- this.sendSubmitResult(
307
- message.messageId,
308
- this.describeSubmissionFailure(error, "Interaction rejected"),
309
- );
310
- }
311
- },
312
- );
313
-
314
- this.unsubscribeHandlers.push(unsubscribe);
315
- }
316
-
317
- private setupValidateInteractionHandler(): void {
318
- if (!this.bridge) return;
319
-
320
- const unsubscribe = this.bridge.onPluginMessage(
321
- "validate-interaction",
322
- async (message) => {
323
- try {
324
- const result = await this.config.onValidateInteraction(
325
- message.playerId,
326
- message.interactionId,
327
- message.params,
328
- );
329
-
330
- this.bridge?.send({
331
- type: "validate-interaction-result",
332
- messageId: message.messageId,
333
- result,
334
- });
335
- } catch (error) {
336
- this.logger.error("[Gateway] Validate interaction error:", error);
337
-
338
- this.bridge?.send({
339
- type: "validate-interaction-result",
340
- messageId: message.messageId,
341
- result: {
342
- valid: false,
343
- errorCode: "validation-error",
344
- message:
345
- error instanceof Error ? error.message : "Validation failed",
346
- },
347
- });
348
- }
349
- },
350
- );
351
-
352
- this.unsubscribeHandlers.push(unsubscribe);
353
- }
354
-
355
- private setupErrorHandler(): void {
356
- if (!this.bridge) return;
357
-
358
- const unsubscribe = this.bridge.onPluginMessage("error", (message) => {
359
- if (this.isRecoverablePluginError(message)) {
360
- this.logger.warn(
361
- "[Gateway] Recoverable plugin submission timeout ignored.",
362
- );
363
- return;
364
- }
365
- this.logger.error("[Gateway] Plugin error:", message.message);
366
- if (message.code) {
367
- this.logger.error("[Gateway] Error code:", message.code);
368
- }
369
- const error = new Error(
370
- message.code ? `${message.code}: ${message.message}` : message.message,
371
- );
372
- error.name = "PluginRuntimeError";
373
- this.handleError(error);
374
- });
375
-
376
- this.unsubscribeHandlers.push(unsubscribe);
377
- }
378
-
379
- private setupSwitchPlayerHandler(): void {
380
- if (!this.bridge) return;
381
-
382
- const unsubscribe = this.bridge.onPluginMessage(
383
- "switch-player",
384
- (message) => {
385
- this.logger.log("[Gateway] Switch player request:", message.playerId);
386
- if (this.config.onSwitchPlayer) {
387
- this.config.onSwitchPlayer(message.playerId);
388
- }
389
- },
390
- );
391
-
392
- this.unsubscribeHandlers.push(unsubscribe);
393
- }
394
-
395
- private isRecoverablePluginError(message: {
396
- code?: string | null;
397
- message: string;
398
- }): boolean {
399
- return (
400
- message.code === "UNHANDLED_REJECTION" &&
401
- message.message.includes("Submission request timed out")
402
- );
403
- }
404
-
405
- private setupRestoreHistoryHandler(): void {
406
- if (!this.bridge) return;
407
-
408
- const unsubscribe = this.bridge.onPluginMessage(
409
- "restore-history",
410
- (message) => {
411
- this.logger.log("[Gateway] Restore history request:", message.entryId);
412
- if (this.config.onRestoreHistory) {
413
- this.config.onRestoreHistory(message.entryId);
414
- }
415
- },
416
- );
417
-
418
- this.unsubscribeHandlers.push(unsubscribe);
419
- }
420
-
421
- private setupStateAckHandler(): void {
422
- if (!this.bridge) return;
423
-
424
- const unsubscribe = this.bridge.onPluginMessage("state-ack", (message) => {
425
- this.logger.log("[Gateway] Received state-ack, syncId:", message.syncId);
426
- // Tier-0 perf: mark `t7_state_sync_received` using the plugin's
427
- // own `Date.now()` captured at state-sync receipt. Uses the
428
- // syncId -> actionId map populated at t5.
429
- if (typeof message.clientReceivedAtMs === "number") {
430
- const actionId = findActionIdBySyncId(message.syncId);
431
- if (actionId) {
432
- recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
433
- timestampMs: message.clientReceivedAtMs,
434
- extra: { syncId: message.syncId, source: "plugin" },
435
- });
436
- }
437
- }
438
- this.gameSessionStore?.onStateAck(message.syncId);
439
- });
440
-
441
- this.unsubscribeHandlers.push(unsubscribe);
442
- }
443
-
444
- private setupStateRenderedHandler(): void {
445
- if (!this.bridge) return;
446
-
447
- const unsubscribe = this.bridge.onPluginMessage(
448
- "state-rendered",
449
- (message) => {
450
- // Tier-0 perf only: the plugin sends this after its post-sync
451
- // microtask/rAF completes so the host HUD can show t8 - t0
452
- // ("total" click-to-render). No functional side effects.
453
- const actionId = findActionIdBySyncId(message.syncId);
454
- if (!actionId) return;
455
- recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
456
- timestampMs: message.clientReceivedAtMs,
457
- extra: { syncId: message.syncId, source: "plugin" },
458
- });
459
- recordMark(actionId, PERF_MARK_NAMES.T8_RENDER_COMMIT, {
460
- timestampMs: message.clientRenderedAtMs,
461
- extra: { syncId: message.syncId, source: "plugin" },
462
- });
463
- },
464
- );
465
-
466
- this.unsubscribeHandlers.push(unsubscribe);
467
- }
468
-
469
- private setupMarkNotificationReadHandler(): void {
470
- if (!this.bridge) return;
471
-
472
- const unsubscribe = this.bridge.onPluginMessage(
473
- "mark-notification-read",
474
- (message) => {
475
- this.logger.log(
476
- "[Gateway] Mark notification read:",
477
- message.notificationId,
478
- );
479
- this.gameSessionStore?.markNotificationRead(message.notificationId);
480
- },
481
- );
482
-
483
- this.unsubscribeHandlers.push(unsubscribe);
484
- }
485
-
486
- private handleError(error: Error): void {
487
- if (this.state === "error") {
488
- return;
489
- }
490
- if (this.readyTimeout) {
491
- clearTimeout(this.readyTimeout);
492
- this.readyTimeout = null;
493
- }
494
- if (this.initStartTimeout) {
495
- clearTimeout(this.initStartTimeout);
496
- this.initStartTimeout = null;
497
- }
498
- this.clearInitRetryInterval();
499
- if (this.healthCheck) {
500
- this.healthCheck.stop();
501
- this.healthCheck = null;
502
- }
503
- this.logger.error("[Gateway] Error:", error);
504
- this.state = "error";
505
- this.config.onError(error);
506
- }
507
-
508
- private sendSubmitResult(
509
- messageId: string,
510
- result: {
511
- accepted: boolean;
512
- errorCode?: string;
513
- message?: string;
514
- },
515
- ): void {
516
- this.bridge?.sendSubmitResult({
517
- messageId,
518
- accepted: result.accepted,
519
- errorCode: result.errorCode,
520
- message: result.message,
521
- });
522
- }
523
-
524
- private describeSubmissionFailure(
525
- error: unknown,
526
- fallbackMessage: string,
527
- ): {
528
- accepted: false;
529
- errorCode?: string;
530
- message: string;
531
- } {
532
- const errorCode =
533
- typeof error === "object" &&
534
- error !== null &&
535
- "errorCode" in error &&
536
- typeof error.errorCode === "string"
537
- ? error.errorCode
538
- : undefined;
539
- const message =
540
- error instanceof Error
541
- ? error.message
542
- : typeof error === "string"
543
- ? error
544
- : fallbackMessage;
545
- return {
546
- accepted: false,
547
- errorCode,
548
- message,
549
- };
550
- }
551
- }
@@ -1,13 +0,0 @@
1
- export * from "../actor-principal.js";
2
- export * from "../gameplay-authority-transport.js";
3
- export * from "../logger.js";
4
- export * from "../host-session-transport.js";
5
- export * from "../perf.js";
6
- export * from "../plugin-bridge.js";
7
- export * from "../plugin-health-check.js";
8
- export * from "../plugin-messages.js";
9
- export * from "../plugin-session-gateway.js";
10
- export * from "../screenshot/projection-to-snapshot.js";
11
- export * from "../screenshot/static-store-api.js";
12
- export * from "../sse-manager.js";
13
- export * from "../unified-session-store.js";
@@ -1,122 +0,0 @@
1
- import type { SeatAssignment } from "@dreamboard-games/api-client";
2
- import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
3
- import { seatsForPluginSnapshot } from "../actor-principal.js";
4
-
5
- const DEFAULT_PLAYER_IDS = [
6
- "player-1",
7
- "player-2",
8
- "player-3",
9
- "player-4",
10
- ] as const;
11
-
12
- const PLAYER_COLORS = [
13
- "#f97316",
14
- "#0ea5e9",
15
- "#22c55e",
16
- "#a855f7",
17
- "#ef4444",
18
- "#eab308",
19
- ] as const;
20
-
21
- export interface ScreenshotProjection {
22
- currentPhase?: string | null;
23
- currentStage?: string | null;
24
- stageSeats?: string[];
25
- simultaneousPhase?: PluginStateSnapshot["gameplay"]["simultaneousPhase"];
26
- view?: unknown;
27
- availableInteractions?: PluginStateSnapshot["gameplay"]["availableInteractions"];
28
- zones?: PluginStateSnapshot["gameplay"]["zones"];
29
- }
30
-
31
- export interface ProjectionToSnapshotOptions {
32
- sessionId?: string | null;
33
- userId?: string | null;
34
- controllingPlayerId?: string;
35
- playerIds?: string[];
36
- syncId?: number;
37
- }
38
-
39
- export function projectionToSnapshot(
40
- projection: ScreenshotProjection,
41
- options: ProjectionToSnapshotOptions = {},
42
- ): PluginStateSnapshot {
43
- const controllingPlayerId =
44
- options.controllingPlayerId ??
45
- projection.stageSeats?.[0] ??
46
- DEFAULT_PLAYER_IDS[0];
47
- const playerIds = normalizePlayerIds(options.playerIds, controllingPlayerId);
48
- const userId = options.userId ?? "screenshot-user";
49
- const seats = buildSeatAssignments(playerIds, userId);
50
-
51
- return {
52
- view: (projection.view ?? null) as PluginStateSnapshot["view"],
53
- gameplay: {
54
- currentPhase:
55
- projection.currentPhase ??
56
- viewCurrentPhase(projection.view) ??
57
- projection.currentStage ??
58
- null,
59
- currentStage: projection.currentStage ?? null,
60
- activePlayers:
61
- projection.stageSeats && projection.stageSeats.length > 0
62
- ? projection.stageSeats
63
- : [controllingPlayerId],
64
- simultaneousPhase: projection.simultaneousPhase ?? null,
65
- availableInteractions: projection.availableInteractions ?? [],
66
- zones: projection.zones ?? {},
67
- },
68
- lobby: {
69
- seats: seatsForPluginSnapshot(seats),
70
- canStart: true,
71
- hostUserId: userId,
72
- },
73
- notifications: [],
74
- session: {
75
- sessionId: options.sessionId ?? null,
76
- controllablePlayerIds: [controllingPlayerId],
77
- controllingPlayerId,
78
- userId,
79
- },
80
- history: null,
81
- syncId: options.syncId ?? 1,
82
- };
83
- }
84
-
85
- function viewCurrentPhase(view: unknown): string | null {
86
- if (!view || typeof view !== "object" || !("currentPhase" in view)) {
87
- return null;
88
- }
89
- const value = (view as { currentPhase?: unknown }).currentPhase;
90
- return typeof value === "string" && value.length > 0 ? value : null;
91
- }
92
-
93
- function normalizePlayerIds(
94
- playerIds: string[] | undefined,
95
- controllingPlayerId: string,
96
- ): string[] {
97
- const normalized = (playerIds?.length ? playerIds : [...DEFAULT_PLAYER_IDS])
98
- .map((playerId) => playerId.trim())
99
- .filter(Boolean);
100
- return Array.from(new Set([controllingPlayerId, ...normalized]));
101
- }
102
-
103
- function buildSeatAssignments(
104
- playerIds: string[],
105
- userId: string,
106
- ): SeatAssignment[] {
107
- return playerIds.map((playerId, index) => ({
108
- playerId,
109
- controllerActor: {
110
- kind: "DEMO_GUEST",
111
- demoActorSessionId: userId,
112
- },
113
- displayName: displayNameForPlayer(playerId),
114
- playerColor: PLAYER_COLORS[index % PLAYER_COLORS.length],
115
- isHost: index === 0,
116
- }));
117
- }
118
-
119
- function displayNameForPlayer(playerId: string): string {
120
- const match = /^player-(\d+)$/.exec(playerId);
121
- return match ? `Player ${match[1]}` : playerId;
122
- }
@@ -1,26 +0,0 @@
1
- import type { PluginStateSnapshot } from "@dreamboard-games/sdk/runtime/runtime-api";
2
- import type { GameSessionStoreApi } from "../plugin-session-gateway.js";
3
-
4
- export function createStaticStoreApi(
5
- snapshot: PluginStateSnapshot,
6
- ): GameSessionStoreApi {
7
- const frozenSnapshot = deepFreeze(snapshot);
8
- return {
9
- getStateSnapshot: () => frozenSnapshot,
10
- subscribe: () => () => undefined,
11
- onStateAck: () => undefined,
12
- markNotificationRead: () => undefined,
13
- };
14
- }
15
-
16
- function deepFreeze<T>(value: T): T {
17
- if (!value || typeof value !== "object" || Object.isFrozen(value)) {
18
- return value;
19
- }
20
-
21
- for (const nested of Object.values(value as Record<string, unknown>)) {
22
- deepFreeze(nested);
23
- }
24
-
25
- return Object.freeze(value);
26
- }