@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,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
- }