@dreamboard-games/cli 0.1.30-alpha.1 → 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.
- package/README.md +179 -22
- package/dist/{chunk-C6UAT6EH.js → chunk-N7XPNNUI.js} +9 -12
- package/dist/chunk-N7XPNNUI.js.map +1 -0
- package/dist/chunk-SEGVTWSK.js +44 -0
- package/dist/{chunk-RS7UXJZV.js → chunk-TAQKH67O.js} +21300 -35881
- package/dist/chunk-TAQKH67O.js.map +1 -0
- package/dist/{global-config-AGFBDFYD.js → global-config-S4ZIPECE.js} +3 -3
- package/dist/index.js +415 -37
- package/dist/index.js.map +1 -1
- package/dist/internal.js +3 -4
- package/dist/{agent-verifier/keychain-backend-TNOPQV3Z.mjs → keychain-backend-HDF4TZDL.js} +2 -1
- package/dist/{agent-verifier/prompt-3BAINGAQ.mjs → prompt-NDV3AE5L.js} +2 -1
- package/package.json +6 -6
- package/skills/dreamboard/references/building-your-first-game.md +510 -0
- package/skills/dreamboard/references/cli.md +104 -0
- package/skills/dreamboard/references/game-interface.md +548 -0
- package/skills/dreamboard/references/manifest-authoring.md +597 -0
- package/skills/dreamboard/references/quickstart.md +66 -0
- package/skills/dreamboard/references/reducer.md +864 -0
- package/skills/dreamboard/references/rule-authoring.md +147 -0
- package/skills/dreamboard/references/testing.md +249 -0
- package/skills/dreamboard/scripts/events-extract.mjs +218 -0
- package/dist/agent-verifier/agent-workspace-verifier.mjs +0 -227
- package/dist/agent-verifier/chunk-2E5P5NWG.mjs +0 -835
- package/dist/agent-verifier/chunk-2GBBP27W.mjs +0 -301
- package/dist/agent-verifier/chunk-2QMNAVV4.mjs +0 -14522
- package/dist/agent-verifier/chunk-2SZHMP6F.mjs +0 -264
- package/dist/agent-verifier/chunk-4WD3YU2E.mjs +0 -166
- package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
- package/dist/agent-verifier/chunk-6A5HRJMQ.mjs +0 -3174
- package/dist/agent-verifier/chunk-6UUJEYDV.mjs +0 -213
- package/dist/agent-verifier/chunk-7653FPGJ.mjs +0 -381
- package/dist/agent-verifier/chunk-7E65UQLY.mjs +0 -38
- package/dist/agent-verifier/chunk-BVVNBJM4.mjs +0 -221
- package/dist/agent-verifier/chunk-CEDUHGNH.mjs +0 -74
- package/dist/agent-verifier/chunk-CEQ2VJWN.mjs +0 -149
- package/dist/agent-verifier/chunk-CFU5EWIC.mjs +0 -69
- package/dist/agent-verifier/chunk-CJEEA6NJ.mjs +0 -730
- package/dist/agent-verifier/chunk-EIQWDQWJ.mjs +0 -186
- package/dist/agent-verifier/chunk-EOQIV6PS.mjs +0 -649
- package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
- package/dist/agent-verifier/chunk-HJFQDSTU.mjs +0 -225
- package/dist/agent-verifier/chunk-JH22JNYD.mjs +0 -1681
- package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
- package/dist/agent-verifier/chunk-LM3OZLZG.mjs +0 -48
- package/dist/agent-verifier/chunk-MINCYHXN.mjs +0 -106
- package/dist/agent-verifier/chunk-MRCUP5SW.mjs +0 -128
- package/dist/agent-verifier/chunk-RBDDIIPM.mjs +0 -19
- package/dist/agent-verifier/chunk-SHUMAVAP.mjs +0 -59
- package/dist/agent-verifier/chunk-SYPLYRGB.mjs +0 -2812
- package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
- package/dist/agent-verifier/chunk-VYJTHSYR.mjs +0 -44
- package/dist/agent-verifier/chunk-XYDL7GY6.mjs +0 -10
- package/dist/agent-verifier/compile-5QSPIOUT.mjs +0 -313
- package/dist/agent-verifier/global-config-WX3ZZIVU.mjs +0 -17
- package/dist/agent-verifier/local-files-MTPLP62S.mjs +0 -46
- package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
- package/dist/agent-verifier/materialize-workspace-FKALAE2T.mjs +0 -90
- package/dist/agent-verifier/project-state-7GR6BQTQ.mjs +0 -32
- package/dist/agent-verifier/reducer-bundle-preflight-C73LEXI2.mjs +0 -23
- package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
- package/dist/agent-verifier/reducer-native-test-harness-GMWBUISX.mjs +0 -53
- package/dist/agent-verifier/static-scaffold-AJMZZQWS.mjs +0 -28
- package/dist/agent-verifier/sync-3DUQH32H.mjs +0 -594
- package/dist/agent-verifier/test-P4U5INTD.mjs +0 -356
- package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
- package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
- package/dist/agent-verifier/workspace-dependencies-HZ6VVS4G.mjs +0 -14
- package/dist/chunk-2H7UOFLK.js +0 -11
- package/dist/chunk-7FOO4AJI.js +0 -50
- package/dist/chunk-7FOO4AJI.js.map +0 -1
- package/dist/chunk-C6UAT6EH.js.map +0 -1
- package/dist/chunk-RS7UXJZV.js.map +0 -1
- package/dist/internal.d.ts +0 -311
- package/dist/keychain-backend-JHTXAKWC.js +0 -135
- package/dist/prompt-GMZABCJC.js +0 -756
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
- package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
- package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
- package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
- package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
- package/dist/testing-KLSV6CPJ.js +0 -674
- package/dist/testing-KLSV6CPJ.js.map +0 -1
- /package/dist/{chunk-2H7UOFLK.js.map → chunk-SEGVTWSK.js.map} +0 -0
- /package/dist/{global-config-AGFBDFYD.js.map → global-config-S4ZIPECE.js.map} +0 -0
- /package/dist/{keychain-backend-JHTXAKWC.js.map → keychain-backend-HDF4TZDL.js.map} +0 -0
- /package/dist/{prompt-GMZABCJC.js.map → prompt-NDV3AE5L.js.map} +0 -0
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getDemoSessionByShortCode,
|
|
3
|
-
getDemoSessionEventBatch,
|
|
4
|
-
getDemoSessionSnapshot,
|
|
5
|
-
getSessionByShortCode,
|
|
6
|
-
getSessionEventBatch,
|
|
7
|
-
getSessionSnapshot,
|
|
8
|
-
startDemoGame,
|
|
9
|
-
startGame,
|
|
10
|
-
} from "@dreamboard-games/api-client";
|
|
11
|
-
import type { ValidationResult } from "@dreamboard-games/sdk/runtime/runtime-api";
|
|
12
|
-
import type {
|
|
13
|
-
HostActionSubmitWireResponse,
|
|
14
|
-
HostSessionWireEvent,
|
|
15
|
-
HostSessionWireSnapshot,
|
|
16
|
-
} from "./session-ingress.js";
|
|
17
|
-
|
|
18
|
-
export interface HostSessionStream {
|
|
19
|
-
stream: AsyncIterable<HostSessionWireEvent | null | undefined>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface HostSessionTransport {
|
|
23
|
-
loadSessionByShortCode(input: {
|
|
24
|
-
shortCode: string;
|
|
25
|
-
requestedPlayerId?: string | null;
|
|
26
|
-
}): Promise<HostSessionWireSnapshot>;
|
|
27
|
-
loadSessionSnapshot(input: {
|
|
28
|
-
sessionId: string;
|
|
29
|
-
requestedPlayerId?: string | null;
|
|
30
|
-
}): Promise<HostSessionWireSnapshot>;
|
|
31
|
-
startSession(input: { sessionId: string }): Promise<HostSessionWireSnapshot>;
|
|
32
|
-
createDevSessionSnapshot?(input: {
|
|
33
|
-
seed?: number | null;
|
|
34
|
-
}): Promise<HostSessionWireSnapshot>;
|
|
35
|
-
submitInteraction(input: {
|
|
36
|
-
sessionId: string;
|
|
37
|
-
playerId: string;
|
|
38
|
-
interactionId: string;
|
|
39
|
-
expectedVersion: number;
|
|
40
|
-
actionSetVersion: string;
|
|
41
|
-
params: unknown;
|
|
42
|
-
clientActionId?: string | null;
|
|
43
|
-
}): Promise<HostActionSubmitWireResponse>;
|
|
44
|
-
validateInteraction(input: {
|
|
45
|
-
sessionId: string;
|
|
46
|
-
playerId: string;
|
|
47
|
-
interactionId: string;
|
|
48
|
-
expectedVersion: number;
|
|
49
|
-
actionSetVersion: string;
|
|
50
|
-
params: unknown;
|
|
51
|
-
}): Promise<ValidationResult>;
|
|
52
|
-
restoreHistory(input: {
|
|
53
|
-
sessionId: string;
|
|
54
|
-
entryId: string;
|
|
55
|
-
}): Promise<HostSessionWireEvent | void>;
|
|
56
|
-
subscribeToSessionEvents(input: {
|
|
57
|
-
sessionId: string;
|
|
58
|
-
clientId: string;
|
|
59
|
-
connectionAttemptId: string;
|
|
60
|
-
clientSource?: string;
|
|
61
|
-
playerId?: string;
|
|
62
|
-
signal?: AbortSignal;
|
|
63
|
-
onSseError?: (error: unknown) => void;
|
|
64
|
-
}): Promise<HostSessionStream>;
|
|
65
|
-
disconnectSessionEvents(input: {
|
|
66
|
-
sessionId: string;
|
|
67
|
-
clientId: string;
|
|
68
|
-
connectionAttemptId: string;
|
|
69
|
-
playerId?: string;
|
|
70
|
-
}): Promise<void>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const SESSION_EVENT_LONG_POLL_WAIT_MS = 25_000;
|
|
74
|
-
const HIDDEN_TAB_EMPTY_POLL_BACKOFF_MS = 5_000;
|
|
75
|
-
|
|
76
|
-
async function requireData<T>(
|
|
77
|
-
result: { data?: T; error?: unknown },
|
|
78
|
-
message: string,
|
|
79
|
-
): Promise<T> {
|
|
80
|
-
if (result.error || !result.data) {
|
|
81
|
-
throw result.error ?? new Error(message);
|
|
82
|
-
}
|
|
83
|
-
return result.data;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function isAbortLikeError(error: unknown): boolean {
|
|
87
|
-
return error instanceof Error && error.name === "AbortError";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isDocumentHidden(): boolean {
|
|
91
|
-
return typeof document !== "undefined" && document.hidden === true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function shouldBackOffHiddenEmptyBatch(batch: {
|
|
95
|
-
timedOut?: boolean;
|
|
96
|
-
events?: unknown[];
|
|
97
|
-
snapshot?: unknown;
|
|
98
|
-
}): boolean {
|
|
99
|
-
return (
|
|
100
|
-
batch.timedOut === true &&
|
|
101
|
-
!batch.snapshot &&
|
|
102
|
-
(!Array.isArray(batch.events) || batch.events.length === 0) &&
|
|
103
|
-
isDocumentHidden()
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function waitForVisibleOrTimeout(
|
|
108
|
-
delayMs: number,
|
|
109
|
-
signal?: AbortSignal,
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
if (delayMs <= 0 || signal?.aborted || !isDocumentHidden()) return;
|
|
112
|
-
|
|
113
|
-
await new Promise<void>((resolve) => {
|
|
114
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
115
|
-
|
|
116
|
-
const cleanup = () => {
|
|
117
|
-
if (timer) {
|
|
118
|
-
clearTimeout(timer);
|
|
119
|
-
timer = null;
|
|
120
|
-
}
|
|
121
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
122
|
-
signal?.removeEventListener("abort", onAbort);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const finish = () => {
|
|
126
|
-
cleanup();
|
|
127
|
-
resolve();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const onVisibilityChange = () => {
|
|
131
|
-
if (!isDocumentHidden()) {
|
|
132
|
-
finish();
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const onAbort = () => {
|
|
137
|
-
finish();
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
timer = setTimeout(finish, delayMs);
|
|
141
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
142
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function* pollSessionEvents(input: {
|
|
147
|
-
sessionId: string;
|
|
148
|
-
clientId: string;
|
|
149
|
-
clientSource?: string;
|
|
150
|
-
playerId?: string;
|
|
151
|
-
signal?: AbortSignal;
|
|
152
|
-
onSseError?: (error: unknown) => void;
|
|
153
|
-
batchRequester?: typeof getSessionEventBatch;
|
|
154
|
-
}): AsyncGenerator<HostSessionWireEvent | null | undefined> {
|
|
155
|
-
let afterCursor: number | undefined;
|
|
156
|
-
const batchRequester = input.batchRequester ?? getSessionEventBatch;
|
|
157
|
-
|
|
158
|
-
while (!input.signal?.aborted) {
|
|
159
|
-
try {
|
|
160
|
-
const batch = await requireData(
|
|
161
|
-
await batchRequester({
|
|
162
|
-
path: { sessionId: input.sessionId },
|
|
163
|
-
query: {
|
|
164
|
-
clientId: input.clientId,
|
|
165
|
-
clientSource: input.clientSource,
|
|
166
|
-
playerId: input.playerId,
|
|
167
|
-
afterCursor,
|
|
168
|
-
waitMs: SESSION_EVENT_LONG_POLL_WAIT_MS,
|
|
169
|
-
},
|
|
170
|
-
signal: input.signal,
|
|
171
|
-
}),
|
|
172
|
-
"Failed to poll session events",
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
afterCursor = batch.cursor;
|
|
176
|
-
|
|
177
|
-
if (batch.snapshot) {
|
|
178
|
-
yield {
|
|
179
|
-
type: "session.snapshot",
|
|
180
|
-
reason: "load",
|
|
181
|
-
snapshot: batch.snapshot,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const event of batch.events) {
|
|
186
|
-
yield event;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (shouldBackOffHiddenEmptyBatch(batch)) {
|
|
190
|
-
await waitForVisibleOrTimeout(
|
|
191
|
-
HIDDEN_TAB_EMPTY_POLL_BACKOFF_MS,
|
|
192
|
-
input.signal,
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
} catch (error) {
|
|
196
|
-
if (input.signal?.aborted || isAbortLikeError(error)) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
input.onSseError?.(error);
|
|
200
|
-
throw error;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const defaultHostSessionTransport: HostSessionTransport = {
|
|
206
|
-
async loadSessionByShortCode(input) {
|
|
207
|
-
return requireData(
|
|
208
|
-
await getSessionByShortCode({
|
|
209
|
-
path: { shortCode: input.shortCode },
|
|
210
|
-
query: input.requestedPlayerId
|
|
211
|
-
? { playerId: input.requestedPlayerId }
|
|
212
|
-
: undefined,
|
|
213
|
-
}),
|
|
214
|
-
"Failed to load session by short code",
|
|
215
|
-
);
|
|
216
|
-
},
|
|
217
|
-
async loadSessionSnapshot(input) {
|
|
218
|
-
return requireData(
|
|
219
|
-
await getSessionSnapshot({
|
|
220
|
-
path: { sessionId: input.sessionId },
|
|
221
|
-
query: input.requestedPlayerId
|
|
222
|
-
? { playerId: input.requestedPlayerId }
|
|
223
|
-
: undefined,
|
|
224
|
-
}),
|
|
225
|
-
"Failed to load session snapshot",
|
|
226
|
-
);
|
|
227
|
-
},
|
|
228
|
-
async startSession(input) {
|
|
229
|
-
return requireData(
|
|
230
|
-
await startGame({ path: { sessionId: input.sessionId } }),
|
|
231
|
-
"Failed to start session",
|
|
232
|
-
);
|
|
233
|
-
},
|
|
234
|
-
async submitInteraction() {
|
|
235
|
-
throw new Error(
|
|
236
|
-
"Backend HTTP gameplay submit has been removed; configure gameplay authority transport.",
|
|
237
|
-
);
|
|
238
|
-
},
|
|
239
|
-
async validateInteraction() {
|
|
240
|
-
return { valid: true };
|
|
241
|
-
},
|
|
242
|
-
async restoreHistory() {
|
|
243
|
-
throw new Error(
|
|
244
|
-
"History restore requires the gameplay authority transport.",
|
|
245
|
-
);
|
|
246
|
-
},
|
|
247
|
-
async subscribeToSessionEvents(input) {
|
|
248
|
-
return { stream: pollSessionEvents(input) };
|
|
249
|
-
},
|
|
250
|
-
async disconnectSessionEvents() {
|
|
251
|
-
// Long-poll requests are bounded and owned by their AbortSignal.
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
export const demoHostSessionTransport: HostSessionTransport = {
|
|
256
|
-
...defaultHostSessionTransport,
|
|
257
|
-
async loadSessionByShortCode(input) {
|
|
258
|
-
return requireData(
|
|
259
|
-
await getDemoSessionByShortCode({
|
|
260
|
-
path: { shortCode: input.shortCode },
|
|
261
|
-
query: input.requestedPlayerId
|
|
262
|
-
? { playerId: input.requestedPlayerId }
|
|
263
|
-
: undefined,
|
|
264
|
-
}),
|
|
265
|
-
"Failed to load demo session by short code",
|
|
266
|
-
);
|
|
267
|
-
},
|
|
268
|
-
async loadSessionSnapshot(input) {
|
|
269
|
-
return requireData(
|
|
270
|
-
await getDemoSessionSnapshot({
|
|
271
|
-
path: { sessionId: input.sessionId },
|
|
272
|
-
query: input.requestedPlayerId
|
|
273
|
-
? { playerId: input.requestedPlayerId }
|
|
274
|
-
: undefined,
|
|
275
|
-
}),
|
|
276
|
-
"Failed to load demo session snapshot",
|
|
277
|
-
);
|
|
278
|
-
},
|
|
279
|
-
async startSession(input) {
|
|
280
|
-
return requireData(
|
|
281
|
-
await startDemoGame({ path: { sessionId: input.sessionId } }),
|
|
282
|
-
"Failed to start demo session",
|
|
283
|
-
);
|
|
284
|
-
},
|
|
285
|
-
async subscribeToSessionEvents(input) {
|
|
286
|
-
return {
|
|
287
|
-
stream: pollSessionEvents({
|
|
288
|
-
...input,
|
|
289
|
-
batchRequester:
|
|
290
|
-
getDemoSessionEventBatch as unknown as typeof getSessionEventBatch,
|
|
291
|
-
}),
|
|
292
|
-
};
|
|
293
|
-
},
|
|
294
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface LoggerLike {
|
|
2
|
-
log: (...args: unknown[]) => void;
|
|
3
|
-
warn: (...args: unknown[]) => void;
|
|
4
|
-
error: (...args: unknown[]) => void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const consoleLogger: LoggerLike = {
|
|
8
|
-
log: (...args) => console.log(...args),
|
|
9
|
-
warn: (...args) => console.warn(...args),
|
|
10
|
-
error: (...args) => console.error(...args),
|
|
11
|
-
};
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dev-only Tier-0 input-latency perf instrumentation.
|
|
3
|
-
*
|
|
4
|
-
* This module is used by the web host (and re-exported for the dev HUD)
|
|
5
|
-
* to record `t0..t8` timing marks for each submitted interaction,
|
|
6
|
-
* keyed by the client-minted `clientActionId`. It is deliberately
|
|
7
|
-
* stateless across page reloads (in-memory ring buffer on `window`)
|
|
8
|
-
* and gated behind `import.meta.env.DEV` or
|
|
9
|
-
* `localStorage.getItem("dreamboard.perf") === "1"` so prod users
|
|
10
|
-
* pay nothing.
|
|
11
|
-
*
|
|
12
|
-
* Design notes
|
|
13
|
-
* - The plugin iframe lives in a separate window/performance context,
|
|
14
|
-
* so plugin-side `performance.mark` entries are not reachable from
|
|
15
|
-
* the host. We work around this by shipping plugin-minted `Date.now()`
|
|
16
|
-
* timestamps across `postMessage` boundaries and recording them on
|
|
17
|
-
* the host-side buffer keyed by `clientActionId`. Date.now() is used
|
|
18
|
-
* (not `performance.now()`) because it shares a wall-clock base
|
|
19
|
-
* across the iframe and the host.
|
|
20
|
-
* - SSE `gameplay.updated` messages do not carry `clientActionId`; they
|
|
21
|
-
* carry `version`. The host records a `version -> actionId` mapping
|
|
22
|
-
* on `t3_http_response` (when the POST action submit response comes back
|
|
23
|
-
* with `version`) so downstream marks (`t4_sse_received`,
|
|
24
|
-
* `t5_store_applied`, `t6_state_sync_posted`) can be stitched by
|
|
25
|
-
* version. Similarly `syncId -> actionId` is captured at `t5` so
|
|
26
|
-
* plugin-side state-ack / state-rendered callbacks (which carry
|
|
27
|
-
* `syncId`) can resolve back to the originating action.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
export const PERF_MARK_NAMES = {
|
|
31
|
-
T0_CLICK: "t0_click",
|
|
32
|
-
T1_HOST_RECEIVED: "t1_host_received",
|
|
33
|
-
T2_HTTP_SENT: "t2_http_sent",
|
|
34
|
-
T3_HTTP_RESPONSE: "t3_http_response",
|
|
35
|
-
/**
|
|
36
|
-
* Fires when the store applies the submitter's gameplay snapshot
|
|
37
|
-
* directly from the HTTP response (the Phase B2 eager-apply path).
|
|
38
|
-
* Sits between `t3_http_response` and `t5_store_applied`; its delta
|
|
39
|
-
* vs `t3_http_response` measures pure apply-in-store cost and its
|
|
40
|
-
* delta vs `t4_sse_received` tells us how much SSE tail the eager
|
|
41
|
-
* apply cuts off for the submitter.
|
|
42
|
-
*/
|
|
43
|
-
T3B_RESPONSE_APPLIED: "t3b_response_applied",
|
|
44
|
-
T4_SSE_RECEIVED: "t4_sse_received",
|
|
45
|
-
T5_STORE_APPLIED: "t5_store_applied",
|
|
46
|
-
T6_STATE_SYNC_POSTED: "t6_state_sync_posted",
|
|
47
|
-
T7_STATE_SYNC_RECEIVED: "t7_state_sync_received",
|
|
48
|
-
T8_RENDER_COMMIT: "t8_render_commit",
|
|
49
|
-
} as const;
|
|
50
|
-
|
|
51
|
-
export type PerfMarkName =
|
|
52
|
-
(typeof PERF_MARK_NAMES)[keyof typeof PERF_MARK_NAMES];
|
|
53
|
-
|
|
54
|
-
export interface PerfMarkRecord {
|
|
55
|
-
name: string;
|
|
56
|
-
timestampMs: number;
|
|
57
|
-
extra?: Record<string, unknown>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface PerfEntry {
|
|
61
|
-
clientActionId: string;
|
|
62
|
-
version?: number;
|
|
63
|
-
syncId?: number;
|
|
64
|
-
createdAtMs: number;
|
|
65
|
-
marks: PerfMarkRecord[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface PerfReceiptMetrics {
|
|
69
|
-
action_submitter_projection_visible_ms?: number;
|
|
70
|
-
action_pending_durability_ms?: number;
|
|
71
|
-
action_durable_confirmation_ms?: number;
|
|
72
|
-
action_other_player_visible_ms?: number;
|
|
73
|
-
action_render_committed_ms?: number;
|
|
74
|
-
action_server_response_ms?: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface PerfReceipt {
|
|
78
|
-
clientActionId: string;
|
|
79
|
-
version?: number;
|
|
80
|
-
syncId?: number;
|
|
81
|
-
metrics: PerfReceiptMetrics;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface PerfBuffer {
|
|
85
|
-
entries: PerfEntry[];
|
|
86
|
-
versionIndex: Map<number, string>;
|
|
87
|
-
syncIdIndex: Map<number, string>;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const GLOBAL_KEY = "__dreamboardPerf__";
|
|
91
|
-
const DUMP_KEY = "__dreamboardPerfDump__";
|
|
92
|
-
const MAX_ENTRIES = 50;
|
|
93
|
-
|
|
94
|
-
type PerfGlobal = typeof globalThis & {
|
|
95
|
-
[GLOBAL_KEY]?: PerfBuffer;
|
|
96
|
-
[DUMP_KEY]?: () => PerfEntry[];
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
function getBuffer(): PerfBuffer | null {
|
|
100
|
-
if (typeof globalThis === "undefined") {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
const scope = globalThis as PerfGlobal;
|
|
104
|
-
const existing = scope[GLOBAL_KEY];
|
|
105
|
-
if (existing) {
|
|
106
|
-
return existing;
|
|
107
|
-
}
|
|
108
|
-
const buffer: PerfBuffer = {
|
|
109
|
-
entries: [],
|
|
110
|
-
versionIndex: new Map(),
|
|
111
|
-
syncIdIndex: new Map(),
|
|
112
|
-
};
|
|
113
|
-
scope[GLOBAL_KEY] = buffer;
|
|
114
|
-
scope[DUMP_KEY] = () => buffer.entries.slice();
|
|
115
|
-
return buffer;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Perf marks + HUD are off by default in prod. Enabled in dev builds
|
|
120
|
-
* or when a maintainer opts in via `localStorage.dreamboard.perf = 1`.
|
|
121
|
-
*/
|
|
122
|
-
export function isPerfEnabled(): boolean {
|
|
123
|
-
if (typeof window === "undefined") {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
const env = (import.meta as unknown as { env?: { DEV?: boolean } }).env;
|
|
128
|
-
if (env?.DEV) {
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
} catch {
|
|
132
|
-
// import.meta may be unavailable in non-Vite contexts; fall through
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
return window.localStorage.getItem("dreamboard.perf") === "1";
|
|
136
|
-
} catch {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function nowMs(): number {
|
|
142
|
-
return Date.now();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function findOrCreateEntry(
|
|
146
|
-
buffer: PerfBuffer,
|
|
147
|
-
clientActionId: string,
|
|
148
|
-
): PerfEntry {
|
|
149
|
-
const existing = buffer.entries.find(
|
|
150
|
-
(entry) => entry.clientActionId === clientActionId,
|
|
151
|
-
);
|
|
152
|
-
if (existing) {
|
|
153
|
-
return existing;
|
|
154
|
-
}
|
|
155
|
-
const created: PerfEntry = {
|
|
156
|
-
clientActionId,
|
|
157
|
-
createdAtMs: nowMs(),
|
|
158
|
-
marks: [],
|
|
159
|
-
};
|
|
160
|
-
buffer.entries.push(created);
|
|
161
|
-
while (buffer.entries.length > MAX_ENTRIES) {
|
|
162
|
-
const evicted = buffer.entries.shift();
|
|
163
|
-
if (!evicted) break;
|
|
164
|
-
if (evicted.version !== undefined) {
|
|
165
|
-
buffer.versionIndex.delete(evicted.version);
|
|
166
|
-
}
|
|
167
|
-
if (evicted.syncId !== undefined) {
|
|
168
|
-
buffer.syncIdIndex.delete(evicted.syncId);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return created;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export interface RecordMarkOptions {
|
|
175
|
-
timestampMs?: number;
|
|
176
|
-
extra?: Record<string, unknown>;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Record a perf mark against a `clientActionId`. No-ops silently when
|
|
181
|
-
* perf is disabled or the runtime has no usable window/globalThis.
|
|
182
|
-
*/
|
|
183
|
-
export function recordMark(
|
|
184
|
-
clientActionId: string | undefined | null,
|
|
185
|
-
name: string,
|
|
186
|
-
options: RecordMarkOptions = {},
|
|
187
|
-
): void {
|
|
188
|
-
if (!clientActionId) return;
|
|
189
|
-
if (!isPerfEnabled()) return;
|
|
190
|
-
const buffer = getBuffer();
|
|
191
|
-
if (!buffer) return;
|
|
192
|
-
|
|
193
|
-
const timestampMs = options.timestampMs ?? nowMs();
|
|
194
|
-
const entry = findOrCreateEntry(buffer, clientActionId);
|
|
195
|
-
entry.marks.push({
|
|
196
|
-
name,
|
|
197
|
-
timestampMs,
|
|
198
|
-
extra: options.extra,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
typeof performance !== "undefined" &&
|
|
203
|
-
typeof performance.mark === "function"
|
|
204
|
-
) {
|
|
205
|
-
try {
|
|
206
|
-
performance.mark(`dreamboard.${name}.${clientActionId}`, {
|
|
207
|
-
detail: { clientActionId, ...options.extra },
|
|
208
|
-
});
|
|
209
|
-
} catch {
|
|
210
|
-
// performance.mark detail arg not supported in older browsers; ignore
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Associate the server-returned `version` (from SubmitInputResponse)
|
|
217
|
-
* with a client-minted actionId so downstream SSE marks keyed by
|
|
218
|
-
* `version` can resolve back to the original action.
|
|
219
|
-
*/
|
|
220
|
-
export function correlateVersion(
|
|
221
|
-
clientActionId: string,
|
|
222
|
-
version: number,
|
|
223
|
-
): void {
|
|
224
|
-
if (!isPerfEnabled()) return;
|
|
225
|
-
const buffer = getBuffer();
|
|
226
|
-
if (!buffer) return;
|
|
227
|
-
const entry = findOrCreateEntry(buffer, clientActionId);
|
|
228
|
-
entry.version = version;
|
|
229
|
-
buffer.versionIndex.set(version, clientActionId);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Associate the local `syncId` assigned when the store applies the
|
|
234
|
-
* gameplay.updated with a clientActionId, so plugin-side state-ack /
|
|
235
|
-
* state-rendered callbacks (which carry syncId rather than actionId)
|
|
236
|
-
* can resolve back to the original action.
|
|
237
|
-
*/
|
|
238
|
-
export function correlateSyncId(clientActionId: string, syncId: number): void {
|
|
239
|
-
if (!isPerfEnabled()) return;
|
|
240
|
-
const buffer = getBuffer();
|
|
241
|
-
if (!buffer) return;
|
|
242
|
-
const entry = findOrCreateEntry(buffer, clientActionId);
|
|
243
|
-
entry.syncId = syncId;
|
|
244
|
-
buffer.syncIdIndex.set(syncId, clientActionId);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function findActionIdByVersion(version: number): string | undefined {
|
|
248
|
-
const buffer = getBuffer();
|
|
249
|
-
return buffer?.versionIndex.get(version);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function findActionIdBySyncId(syncId: number): string | undefined {
|
|
253
|
-
const buffer = getBuffer();
|
|
254
|
-
return buffer?.syncIdIndex.get(syncId);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function getPerfEntries(): PerfEntry[] {
|
|
258
|
-
const buffer = getBuffer();
|
|
259
|
-
return buffer ? buffer.entries.slice() : [];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function getPerfReceipts(): PerfReceipt[] {
|
|
263
|
-
return getPerfEntries().map((entry) => ({
|
|
264
|
-
clientActionId: entry.clientActionId,
|
|
265
|
-
version: entry.version,
|
|
266
|
-
syncId: entry.syncId,
|
|
267
|
-
metrics: {
|
|
268
|
-
action_submitter_projection_visible_ms: deltaFor(
|
|
269
|
-
entry,
|
|
270
|
-
PERF_MARK_NAMES.T0_CLICK,
|
|
271
|
-
PERF_MARK_NAMES.T3B_RESPONSE_APPLIED,
|
|
272
|
-
),
|
|
273
|
-
action_pending_durability_ms: deltaFor(
|
|
274
|
-
entry,
|
|
275
|
-
PERF_MARK_NAMES.T3B_RESPONSE_APPLIED,
|
|
276
|
-
PERF_MARK_NAMES.T4_SSE_RECEIVED,
|
|
277
|
-
),
|
|
278
|
-
action_durable_confirmation_ms: deltaFor(
|
|
279
|
-
entry,
|
|
280
|
-
PERF_MARK_NAMES.T0_CLICK,
|
|
281
|
-
PERF_MARK_NAMES.T4_SSE_RECEIVED,
|
|
282
|
-
),
|
|
283
|
-
action_other_player_visible_ms: deltaFor(
|
|
284
|
-
entry,
|
|
285
|
-
PERF_MARK_NAMES.T0_CLICK,
|
|
286
|
-
PERF_MARK_NAMES.T5_STORE_APPLIED,
|
|
287
|
-
),
|
|
288
|
-
action_render_committed_ms: deltaFor(
|
|
289
|
-
entry,
|
|
290
|
-
PERF_MARK_NAMES.T0_CLICK,
|
|
291
|
-
PERF_MARK_NAMES.T8_RENDER_COMMIT,
|
|
292
|
-
),
|
|
293
|
-
action_server_response_ms: deltaFor(
|
|
294
|
-
entry,
|
|
295
|
-
PERF_MARK_NAMES.T2_HTTP_SENT,
|
|
296
|
-
PERF_MARK_NAMES.T3_HTTP_RESPONSE,
|
|
297
|
-
),
|
|
298
|
-
},
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** Drop every recorded entry; used by tests and the HUD "Clear" action. */
|
|
303
|
-
export function clearPerfEntries(): void {
|
|
304
|
-
const buffer = getBuffer();
|
|
305
|
-
if (!buffer) return;
|
|
306
|
-
buffer.entries = [];
|
|
307
|
-
buffer.versionIndex.clear();
|
|
308
|
-
buffer.syncIdIndex.clear();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function firstMarkMs(entry: PerfEntry, name: string): number | undefined {
|
|
312
|
-
return entry.marks.find((mark) => mark.name === name)?.timestampMs;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function deltaFor(
|
|
316
|
-
entry: PerfEntry,
|
|
317
|
-
startName: string,
|
|
318
|
-
endName: string,
|
|
319
|
-
): number | undefined {
|
|
320
|
-
const start = firstMarkMs(entry, startName);
|
|
321
|
-
const end = firstMarkMs(entry, endName);
|
|
322
|
-
if (start === undefined || end === undefined) return undefined;
|
|
323
|
-
return Math.round((end - start) * 10) / 10;
|
|
324
|
-
}
|