@dreamboard-games/sdk 0.5.0-alpha.2 → 0.5.0-alpha.4
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 +186 -45
- package/dist/board-target-B0QG6OYx.d.ts +8 -0
- package/dist/chunk-3JXUL4VN.js +139 -0
- package/dist/chunk-3JXUL4VN.js.map +1 -0
- package/dist/{chunk-S2JEDVWH.js → chunk-C77SYIVT.js} +6102 -6505
- package/dist/chunk-C77SYIVT.js.map +1 -0
- package/dist/chunk-DMFVAZI2.js +16 -0
- package/dist/chunk-DMFVAZI2.js.map +1 -0
- package/dist/chunk-OOFMBCFF.js +1338 -0
- package/dist/chunk-OOFMBCFF.js.map +1 -0
- package/dist/chunk-UG2Q4NLG.js +590 -0
- package/dist/chunk-UG2Q4NLG.js.map +1 -0
- package/dist/chunk-XU6WJW6K.js +847 -0
- package/dist/chunk-XU6WJW6K.js.map +1 -0
- package/dist/diagnostics-B8d5aLad.d.ts +65 -0
- package/dist/extract-gVKcB8Og.d.ts +4736 -0
- package/dist/index.d.ts +3782 -1
- package/dist/index.js +1096 -6
- package/dist/index.js.map +1 -1
- package/dist/model-vdpLHrxh.d.ts +432 -0
- package/dist/react.d.ts +60 -0
- package/dist/react.js +208 -0
- package/dist/react.js.map +1 -0
- package/dist/reducer.d.ts +1244 -78
- package/dist/reducer.js +4204 -121
- package/dist/reducer.js.map +1 -1
- package/dist/testing.d.ts +520 -564
- package/dist/testing.js +2451 -915
- package/dist/testing.js.map +1 -1
- package/dist/types-Bj5ZLTUV.d.ts +114 -0
- package/package.json +40 -142
- package/dist/HandView-Bi2lxzQP.d.ts +0 -79
- package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
- package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
- package/dist/attributes-DbvyMbXw.d.ts +0 -68
- package/dist/browser-interaction.d.ts +0 -910
- package/dist/browser-interaction.js +0 -119
- package/dist/browser-interaction.js.map +0 -1
- package/dist/chunk-32PFKDV7.js +0 -11705
- package/dist/chunk-32PFKDV7.js.map +0 -1
- package/dist/chunk-3BAD4VC2.js +0 -263
- package/dist/chunk-3BAD4VC2.js.map +0 -1
- package/dist/chunk-3SKDNDPG.js +0 -104
- package/dist/chunk-3SKDNDPG.js.map +0 -1
- package/dist/chunk-5YV2WHJ4.js +0 -25
- package/dist/chunk-5YV2WHJ4.js.map +0 -1
- package/dist/chunk-ANRB4XKA.js +0 -90
- package/dist/chunk-ANRB4XKA.js.map +0 -1
- package/dist/chunk-BI4G3PM2.js +0 -1864
- package/dist/chunk-BI4G3PM2.js.map +0 -1
- package/dist/chunk-EVCLTY4Q.js +0 -935
- package/dist/chunk-EVCLTY4Q.js.map +0 -1
- package/dist/chunk-GDPKTBUR.js +0 -564
- package/dist/chunk-GDPKTBUR.js.map +0 -1
- package/dist/chunk-H6VDGFL5.js +0 -2903
- package/dist/chunk-H6VDGFL5.js.map +0 -1
- package/dist/chunk-LR3ZTWQF.js +0 -5960
- package/dist/chunk-LR3ZTWQF.js.map +0 -1
- package/dist/chunk-PF7L4BMG.js +0 -117
- package/dist/chunk-PF7L4BMG.js.map +0 -1
- package/dist/chunk-PN5O6GG2.js +0 -32
- package/dist/chunk-PN5O6GG2.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S2JEDVWH.js.map +0 -1
- package/dist/chunk-T3ZKNUZ7.js +0 -1
- package/dist/chunk-T3ZKNUZ7.js.map +0 -1
- package/dist/chunk-UGH54WZ2.js +0 -223
- package/dist/chunk-UGH54WZ2.js.map +0 -1
- package/dist/chunk-VCDWP5VL.js +0 -746
- package/dist/chunk-VCDWP5VL.js.map +0 -1
- package/dist/chunk-VDXOF4FW.js +0 -69
- package/dist/chunk-VDXOF4FW.js.map +0 -1
- package/dist/chunk-WHR5UW3F.js +0 -1988
- package/dist/chunk-WHR5UW3F.js.map +0 -1
- package/dist/chunk-Y75CFE77.js +0 -17
- package/dist/chunk-Y75CFE77.js.map +0 -1
- package/dist/chunk-Z7QCREWI.js +0 -3435
- package/dist/chunk-Z7QCREWI.js.map +0 -1
- package/dist/chunk-ZZGN2ATS.js +0 -1941
- package/dist/chunk-ZZGN2ATS.js.map +0 -1
- package/dist/components-gAbIz7hK.d.ts +0 -1284
- package/dist/definitions-BgmV_GhC.d.ts +0 -300
- package/dist/diagnostics-DIi3ina7.d.ts +0 -50
- package/dist/digest.d.ts +0 -8
- package/dist/game-CsZScpcn.d.ts +0 -763
- package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
- package/dist/index-CcT9Q7N7.d.ts +0 -193
- package/dist/index.d-DjzoK7zn.d.ts +0 -2101
- package/dist/package-set.d.ts +0 -13
- package/dist/package-set.js +0 -12
- package/dist/package-set.js.map +0 -1
- package/dist/player-state-Cqpyeql0.d.ts +0 -371
- package/dist/plugin-runtime-contract.d.ts +0 -17
- package/dist/plugin-runtime-contract.js +0 -92
- package/dist/plugin-runtime-contract.js.map +0 -1
- package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
- package/dist/protocol-dYgafTYY.d.ts +0 -314
- package/dist/reducer/advanced.d.ts +0 -82
- package/dist/reducer/advanced.js +0 -51
- package/dist/reducer/advanced.js.map +0 -1
- package/dist/reducer-contract.d.ts +0 -11
- package/dist/reducer-contract.js +0 -16
- package/dist/reducer-contract.js.map +0 -1
- package/dist/reference-games/index.d.ts +0 -31
- package/dist/reference-games/index.js +0 -48
- package/dist/reference-games/index.js.map +0 -1
- package/dist/runtime/primitives.d.ts +0 -250
- package/dist/runtime/primitives.js +0 -189
- package/dist/runtime/primitives.js.map +0 -1
- package/dist/runtime/runtime-api.d.ts +0 -2
- package/dist/runtime/runtime-api.js +0 -1
- package/dist/runtime/runtime-api.js.map +0 -1
- package/dist/runtime/workspace-contract.d.ts +0 -392
- package/dist/runtime/workspace-contract.js +0 -28
- package/dist/runtime/workspace-contract.js.map +0 -1
- package/dist/runtime-RJ5orDJU.d.ts +0 -1699
- package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
- package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
- package/dist/runtime.d.ts +0 -83
- package/dist/runtime.js +0 -242
- package/dist/runtime.js.map +0 -1
- package/dist/schema.d.ts +0 -379
- package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
- package/dist/testing-compiler.d.ts +0 -37
- package/dist/testing-compiler.js +0 -272
- package/dist/testing-compiler.js.map +0 -1
- package/dist/testing-runtime.d.ts +0 -119
- package/dist/testing-runtime.js +0 -212
- package/dist/testing-runtime.js.map +0 -1
- package/dist/types-DJj5MJkl.d.ts +0 -256
- package/dist/types-DR7DoB1x.d.ts +0 -28
- package/dist/types-JWCYHmu7.d.ts +0 -122
- package/dist/types.d.ts +0 -1958
- package/dist/types.js +0 -14
- package/dist/types.js.map +0 -1
- package/dist/ui/components.d.ts +0 -17
- package/dist/ui/components.js +0 -216
- package/dist/ui/components.js.map +0 -1
- package/dist/ui/defaults.d.ts +0 -19
- package/dist/ui/defaults.js +0 -104
- package/dist/ui/defaults.js.map +0 -1
- package/dist/ui/player-state.d.ts +0 -2
- package/dist/ui/player-state.js +0 -1
- package/dist/ui/player-state.js.map +0 -1
- package/dist/ui/plugin-styles.css +0 -2
- package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
- package/dist/ui.d.ts +0 -317
- package/dist/ui.js +0 -277
- package/dist/ui.js.map +0 -1
- package/dist/views-BpuWcOyN.d.ts +0 -1641
package/dist/index.js
CHANGED
|
@@ -1,12 +1,1102 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
AmbiguousTargetError,
|
|
3
|
+
createGameInstance
|
|
4
|
+
} from "./chunk-OOFMBCFF.js";
|
|
5
|
+
import {
|
|
6
|
+
BoardStaticProjectionSchema,
|
|
7
|
+
CancelInteractionCommandSchema,
|
|
8
|
+
DREAMBOARD_PLUGIN_PROTOCOL,
|
|
9
|
+
DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
|
|
10
|
+
GameEventDetailSchema,
|
|
11
|
+
GameEventSchema,
|
|
12
|
+
GameOutcomeSchema,
|
|
13
|
+
GameplayBasisSchema,
|
|
14
|
+
HostToPluginEnvelopeSchema,
|
|
15
|
+
HostToPluginPayloadSchema,
|
|
16
|
+
InteractionResultSchema,
|
|
17
|
+
PluginGameplayFrameSchema,
|
|
18
|
+
PluginPlayerSummarySchema,
|
|
19
|
+
PluginSessionDescriptorSchema,
|
|
20
|
+
PluginToHostEnvelopeSchema,
|
|
21
|
+
PluginToHostPayloadSchema,
|
|
22
|
+
SeatFrameSchema,
|
|
23
|
+
SeatProjectionBundleSchema,
|
|
24
|
+
SimultaneousPhaseSnapshotSchema,
|
|
25
|
+
SubmitInteractionCommandSchema,
|
|
26
|
+
SystemActionEventSchema,
|
|
27
|
+
ZoneHandlesSnapshotSchema,
|
|
28
|
+
computePluginActionSetVersion,
|
|
29
|
+
createPluginProtocolEnvelopeSchema,
|
|
30
|
+
createSourceLifecycle,
|
|
31
|
+
digestPluginCommandRequest,
|
|
32
|
+
digestPluginGameplayFrame,
|
|
33
|
+
materializePluginGameplayFrame,
|
|
34
|
+
parseHostToPluginEnvelope,
|
|
35
|
+
parsePluginGameplayFrame,
|
|
36
|
+
parsePluginSessionDescriptor,
|
|
37
|
+
parsePluginToHostEnvelope,
|
|
38
|
+
staticSource
|
|
39
|
+
} from "./chunk-UG2Q4NLG.js";
|
|
40
|
+
import "./chunk-DMFVAZI2.js";
|
|
41
|
+
import {
|
|
42
|
+
InputDomainSchema,
|
|
43
|
+
InteractionAvailabilitySchema,
|
|
44
|
+
InteractionCommitPolicySchema,
|
|
45
|
+
InteractionDescriptorSchema,
|
|
46
|
+
InteractionInputDescriptorSchema,
|
|
47
|
+
RuntimeJsonSchema,
|
|
48
|
+
canonicalizePluginRuntimeJson,
|
|
49
|
+
createHexBoardGeometry,
|
|
50
|
+
digestPluginRuntimeJson,
|
|
51
|
+
encodeCanonicalPluginRuntimeJson
|
|
52
|
+
} from "./chunk-XU6WJW6K.js";
|
|
53
|
+
import {
|
|
54
|
+
inputValueKey,
|
|
55
|
+
requireLookup
|
|
56
|
+
} from "./chunk-3JXUL4VN.js";
|
|
57
|
+
|
|
58
|
+
// src/package-set.ts
|
|
59
|
+
var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.4" : "0.0.0-development";
|
|
60
|
+
var DREAMBOARD_SDK_PACKAGES = {
|
|
61
|
+
"@dreamboard-games/sdk": DREAMBOARD_SDK_VERSION
|
|
62
|
+
};
|
|
63
|
+
var DREAMBOARD_SDK_PACKAGE_SET = {
|
|
64
|
+
version: 1,
|
|
65
|
+
sdkVersion: DREAMBOARD_SDK_VERSION,
|
|
66
|
+
packages: DREAMBOARD_SDK_PACKAGES
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/headless/sources/iframe.ts
|
|
70
|
+
function iframeSource(options = {}) {
|
|
71
|
+
const target = window;
|
|
72
|
+
const parent = target.parent;
|
|
73
|
+
let channel = null;
|
|
74
|
+
let sequence = 0;
|
|
75
|
+
let receivedSequence = -1;
|
|
76
|
+
const send = (payload) => {
|
|
77
|
+
if (!channel) throw new Error("Iframe source is not initialized.");
|
|
78
|
+
parent.postMessage(
|
|
79
|
+
{
|
|
80
|
+
protocol: DREAMBOARD_PLUGIN_PROTOCOL,
|
|
81
|
+
version: DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
|
|
82
|
+
channelId: channel.id,
|
|
83
|
+
sequence: ++sequence,
|
|
84
|
+
payload
|
|
85
|
+
},
|
|
86
|
+
channel.origin
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
const lifecycle = createSourceLifecycle({
|
|
90
|
+
timeoutMs: options.timeoutMs,
|
|
91
|
+
send,
|
|
92
|
+
recover: () => send({ type: "runtime.resume" }),
|
|
93
|
+
close: () => target.removeEventListener("message", receive)
|
|
94
|
+
});
|
|
95
|
+
const isOpen = () => {
|
|
96
|
+
const { connection } = lifecycle.source.store.get();
|
|
97
|
+
return connection !== "closed" && connection !== "failed";
|
|
98
|
+
};
|
|
99
|
+
function receive(event) {
|
|
100
|
+
if (event.source !== parent) return;
|
|
101
|
+
const parsed = HostToPluginEnvelopeSchema.safeParse(event.data);
|
|
102
|
+
if (!parsed.success) {
|
|
103
|
+
const data = event.data;
|
|
104
|
+
if (channel && event.origin === channel.origin && typeof data === "object" && data !== null && "channelId" in data && data.channelId === channel.id) {
|
|
105
|
+
try {
|
|
106
|
+
send({
|
|
107
|
+
type: "runtime.error",
|
|
108
|
+
code: "host-runtime-protocol-mismatch",
|
|
109
|
+
message: "Iframe source received an invalid host envelope."
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
lifecycle.fail(new Error("Invalid iframe host envelope."));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const envelope = parsed.data;
|
|
118
|
+
if (!channel) {
|
|
119
|
+
if (envelope.payload.type !== "runtime.init") return;
|
|
120
|
+
channel = { id: envelope.channelId, origin: event.origin };
|
|
121
|
+
}
|
|
122
|
+
if (channel.id !== envelope.channelId || channel.origin !== event.origin || envelope.sequence <= receivedSequence)
|
|
123
|
+
return;
|
|
124
|
+
receivedSequence = envelope.sequence;
|
|
125
|
+
const payload = envelope.payload;
|
|
126
|
+
if (payload.type === "runtime.init") {
|
|
127
|
+
lifecycle.session(payload.session);
|
|
128
|
+
if (isOpen()) send({ type: "runtime.ready" });
|
|
129
|
+
} else if (payload.type === "gameplay.frame")
|
|
130
|
+
lifecycle.frame(payload.frame);
|
|
131
|
+
else lifecycle.result(payload);
|
|
132
|
+
if (isOpen()) send({ type: "runtime.ack", sequence: envelope.sequence });
|
|
133
|
+
}
|
|
134
|
+
target.addEventListener("message", receive);
|
|
135
|
+
return lifecycle.source;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/shared/protocol/gameplay-wire.ts
|
|
139
|
+
import { z } from "zod";
|
|
140
|
+
var GameplayCloseCode = {
|
|
141
|
+
GoingAway: 1001,
|
|
142
|
+
CredentialExpired: 4e3,
|
|
143
|
+
CredentialInvalid: 4001,
|
|
144
|
+
RefreshContextMismatch: 4002,
|
|
145
|
+
PermissionDenied: 4100,
|
|
146
|
+
ProtocolViolation: 4200,
|
|
147
|
+
FrameBeforeAuth: 4201,
|
|
148
|
+
SlowConsumer: 4300,
|
|
149
|
+
TooManyRequests: 4301
|
|
150
|
+
};
|
|
151
|
+
var GameplayCredentialSchema = z.discriminatedUnion("kind", [
|
|
152
|
+
z.object({ kind: z.literal("user"), token: z.string().min(1).max(16384) }).strict(),
|
|
153
|
+
z.object({ kind: z.literal("demo"), secret: z.string().min(1).max(1024) }).strict()
|
|
154
|
+
]);
|
|
155
|
+
var AuthConnectFrameSchema = z.object({
|
|
156
|
+
type: z.literal("auth.connect"),
|
|
157
|
+
credential: GameplayCredentialSchema,
|
|
158
|
+
sessionId: z.string().uuid(),
|
|
159
|
+
playerId: z.string().min(1).max(128)
|
|
160
|
+
}).strict();
|
|
161
|
+
var AuthRefreshFrameSchema = z.object({
|
|
162
|
+
type: z.literal("auth.refresh"),
|
|
163
|
+
credential: GameplayCredentialSchema
|
|
164
|
+
}).strict();
|
|
165
|
+
var SessionResumeFrameSchema = z.object({
|
|
166
|
+
type: z.literal("session.resume"),
|
|
167
|
+
lastSeenLogCursor: z.number().int().nonnegative().nullable(),
|
|
168
|
+
unacknowledgedClientActionIds: z.array(z.string().min(1)).max(32)
|
|
169
|
+
});
|
|
170
|
+
var AuthAcceptedFrameSchema = z.object({
|
|
171
|
+
type: z.literal("auth.accepted"),
|
|
172
|
+
expiresAt: z.string().datetime()
|
|
173
|
+
});
|
|
174
|
+
var GameplayBackpressureReasonSchema = z.enum([
|
|
175
|
+
"queue_full",
|
|
176
|
+
"capacity_full"
|
|
177
|
+
]);
|
|
178
|
+
var GameplayBackpressureOperationSchema = z.enum([
|
|
179
|
+
"session.resume",
|
|
180
|
+
"interaction.submit",
|
|
181
|
+
"interaction.cancel"
|
|
182
|
+
]);
|
|
183
|
+
var GameplayBackpressureFrameSchema = z.object({
|
|
184
|
+
type: z.literal("gameplay.backpressure"),
|
|
185
|
+
reason: GameplayBackpressureReasonSchema,
|
|
186
|
+
retryAfterMs: z.number().int().positive(),
|
|
187
|
+
message: z.string().min(1),
|
|
188
|
+
operation: GameplayBackpressureOperationSchema,
|
|
189
|
+
clientActionId: z.string().min(1).optional()
|
|
190
|
+
});
|
|
191
|
+
var SessionSnapshotFrameSchema = z.object({
|
|
192
|
+
type: z.literal("session.snapshot"),
|
|
193
|
+
frame: PluginGameplayFrameSchema
|
|
194
|
+
});
|
|
195
|
+
var ClientGameplayFrameSchema = z.discriminatedUnion("type", [
|
|
196
|
+
AuthConnectFrameSchema,
|
|
197
|
+
AuthRefreshFrameSchema,
|
|
198
|
+
SessionResumeFrameSchema,
|
|
199
|
+
SubmitInteractionCommandSchema,
|
|
200
|
+
CancelInteractionCommandSchema
|
|
201
|
+
]);
|
|
202
|
+
var ServerGameplayFrameSchema = z.discriminatedUnion("type", [
|
|
203
|
+
AuthAcceptedFrameSchema,
|
|
204
|
+
GameplayBackpressureFrameSchema,
|
|
205
|
+
SessionSnapshotFrameSchema,
|
|
206
|
+
InteractionResultSchema
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
// src/headless/sources/host-websocket.ts
|
|
210
|
+
function hostSource(options) {
|
|
211
|
+
const { url, playerId, getCredential } = options;
|
|
212
|
+
const sessionId = options.session.sessionId;
|
|
213
|
+
let socket = null;
|
|
214
|
+
let generation = 0;
|
|
215
|
+
let disposed = false;
|
|
216
|
+
let authenticated = false;
|
|
217
|
+
let queued = null;
|
|
218
|
+
let reconnects = 0;
|
|
219
|
+
let refreshTimer;
|
|
220
|
+
let snapshotTimer;
|
|
221
|
+
let openTimer;
|
|
222
|
+
let cleanup = () => {
|
|
223
|
+
};
|
|
224
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
225
|
+
const send = (frame) => {
|
|
226
|
+
if (!socket || socket.readyState !== WebSocket.OPEN)
|
|
227
|
+
throw new Error("Gameplay socket is not open.");
|
|
228
|
+
socket.send(JSON.stringify(ClientGameplayFrameSchema.parse(frame)));
|
|
229
|
+
};
|
|
230
|
+
const resume = () => send({
|
|
231
|
+
type: "session.resume",
|
|
232
|
+
lastSeenLogCursor: null,
|
|
233
|
+
unacknowledgedClientActionIds: queued ? [queued.clientActionId] : []
|
|
234
|
+
});
|
|
235
|
+
const lifecycle = createSourceLifecycle({
|
|
236
|
+
context: {
|
|
237
|
+
sessionId,
|
|
238
|
+
playerId
|
|
239
|
+
},
|
|
240
|
+
timeoutMs,
|
|
241
|
+
send(command) {
|
|
242
|
+
if (!authenticated) {
|
|
243
|
+
queued = command;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
send(command);
|
|
247
|
+
},
|
|
248
|
+
recover() {
|
|
249
|
+
if (authenticated) resume();
|
|
250
|
+
else if (!openTimer) void connect();
|
|
251
|
+
},
|
|
252
|
+
close() {
|
|
253
|
+
disposed = true;
|
|
254
|
+
generation++;
|
|
255
|
+
clearTimeout(refreshTimer);
|
|
256
|
+
clearTimeout(openTimer);
|
|
257
|
+
clearTimeout(snapshotTimer);
|
|
258
|
+
cleanup();
|
|
259
|
+
socket?.close();
|
|
260
|
+
socket = null;
|
|
261
|
+
queued = null;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
lifecycle.session(options.session);
|
|
265
|
+
async function connect() {
|
|
266
|
+
if (disposed) return;
|
|
267
|
+
const current = ++generation;
|
|
268
|
+
authenticated = false;
|
|
269
|
+
clearTimeout(refreshTimer);
|
|
270
|
+
clearTimeout(openTimer);
|
|
271
|
+
clearTimeout(snapshotTimer);
|
|
272
|
+
cleanup();
|
|
273
|
+
socket?.close();
|
|
274
|
+
socket = null;
|
|
275
|
+
openTimer = setTimeout(
|
|
276
|
+
() => lifecycle.fail(new Error("Gameplay socket authentication timed out.")),
|
|
277
|
+
timeoutMs
|
|
278
|
+
);
|
|
279
|
+
try {
|
|
280
|
+
const credential = await getCredential();
|
|
281
|
+
if (disposed || current !== generation) return;
|
|
282
|
+
const next = new WebSocket(url);
|
|
283
|
+
socket = next;
|
|
284
|
+
const active = () => !disposed && current === generation;
|
|
285
|
+
const onOpen = () => {
|
|
286
|
+
if (!active()) return;
|
|
287
|
+
try {
|
|
288
|
+
send({
|
|
289
|
+
type: "auth.connect",
|
|
290
|
+
credential,
|
|
291
|
+
sessionId,
|
|
292
|
+
playerId
|
|
293
|
+
});
|
|
294
|
+
} catch (error) {
|
|
295
|
+
lifecycle.fail(asError(error));
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const onMessage = (event) => {
|
|
299
|
+
if (!active()) return;
|
|
300
|
+
try {
|
|
301
|
+
const value = JSON.parse(String(event.data));
|
|
302
|
+
if (typeof value !== "object" || value === null || !("type" in value))
|
|
303
|
+
throw new Error("Invalid gameplay frame.");
|
|
304
|
+
if (![
|
|
305
|
+
"auth.accepted",
|
|
306
|
+
"session.snapshot",
|
|
307
|
+
"interaction.result",
|
|
308
|
+
"gameplay.backpressure"
|
|
309
|
+
].includes(String(value.type)))
|
|
310
|
+
return;
|
|
311
|
+
const frame = ServerGameplayFrameSchema.parse(value);
|
|
312
|
+
if (frame.type === "auth.accepted") {
|
|
313
|
+
clearTimeout(openTimer);
|
|
314
|
+
openTimer = void 0;
|
|
315
|
+
const first = !authenticated;
|
|
316
|
+
authenticated = true;
|
|
317
|
+
if (first) {
|
|
318
|
+
snapshotTimer = setTimeout(
|
|
319
|
+
() => lifecycle.fail(new Error("Gameplay snapshot timed out.")),
|
|
320
|
+
timeoutMs
|
|
321
|
+
);
|
|
322
|
+
resume();
|
|
323
|
+
if (queued) {
|
|
324
|
+
send(queued);
|
|
325
|
+
queued = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
clearTimeout(refreshTimer);
|
|
329
|
+
if (credential.kind === "user") {
|
|
330
|
+
const ttl = Date.parse(frame.expiresAt) - Date.now();
|
|
331
|
+
if (ttl <= 0)
|
|
332
|
+
throw new Error("Gameplay credential is already expired.");
|
|
333
|
+
refreshTimer = setTimeout(
|
|
334
|
+
() => {
|
|
335
|
+
openTimer = setTimeout(
|
|
336
|
+
() => lifecycle.fail(
|
|
337
|
+
new Error("Gameplay credential refresh timed out.")
|
|
338
|
+
),
|
|
339
|
+
timeoutMs
|
|
340
|
+
);
|
|
341
|
+
void getCredential().then((updated) => {
|
|
342
|
+
if (active())
|
|
343
|
+
send({ type: "auth.refresh", credential: updated });
|
|
344
|
+
}).catch((error) => {
|
|
345
|
+
if (active()) lifecycle.fail(asError(error));
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
Math.max(0, ttl - Math.min(6e4, ttl * 0.2))
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
} else if (!authenticated)
|
|
352
|
+
throw new Error("Gameplay frame arrived before authentication.");
|
|
353
|
+
else if (frame.type === "session.snapshot") {
|
|
354
|
+
clearTimeout(snapshotTimer);
|
|
355
|
+
snapshotTimer = void 0;
|
|
356
|
+
reconnects = 0;
|
|
357
|
+
lifecycle.frame(frame.frame);
|
|
358
|
+
} else if (frame.type === "interaction.result")
|
|
359
|
+
lifecycle.result(frame);
|
|
360
|
+
else lifecycle.fail(new Error(frame.message));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
lifecycle.fail(asError(error));
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const onClose = (event) => {
|
|
366
|
+
if (!active()) return;
|
|
367
|
+
authenticated = false;
|
|
368
|
+
lifecycle.recovering();
|
|
369
|
+
if ((/* @__PURE__ */ new Set([
|
|
370
|
+
GameplayCloseCode.CredentialInvalid,
|
|
371
|
+
GameplayCloseCode.RefreshContextMismatch,
|
|
372
|
+
GameplayCloseCode.PermissionDenied,
|
|
373
|
+
GameplayCloseCode.ProtocolViolation,
|
|
374
|
+
GameplayCloseCode.FrameBeforeAuth
|
|
375
|
+
])).has(event.code) || reconnects++ > 0) {
|
|
376
|
+
lifecycle.fail(new Error(`Gameplay socket closed (${event.code}).`));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
void connect();
|
|
380
|
+
lifecycle.retry();
|
|
381
|
+
};
|
|
382
|
+
const onError = () => {
|
|
383
|
+
if (active()) next.close();
|
|
384
|
+
};
|
|
385
|
+
next.addEventListener("open", onOpen);
|
|
386
|
+
next.addEventListener("message", onMessage);
|
|
387
|
+
next.addEventListener("close", onClose);
|
|
388
|
+
next.addEventListener("error", onError);
|
|
389
|
+
cleanup = () => {
|
|
390
|
+
next.removeEventListener("open", onOpen);
|
|
391
|
+
next.removeEventListener("message", onMessage);
|
|
392
|
+
next.removeEventListener("close", onClose);
|
|
393
|
+
next.removeEventListener("error", onError);
|
|
394
|
+
};
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (!disposed && current === generation) lifecycle.fail(asError(error));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
void connect();
|
|
400
|
+
return lifecycle.source;
|
|
401
|
+
}
|
|
402
|
+
function asError(error) {
|
|
403
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/headless/features/hand.ts
|
|
407
|
+
function handFeature(_game, options = {}) {
|
|
408
|
+
return {
|
|
409
|
+
zone: {
|
|
410
|
+
getSortedCardIds() {
|
|
411
|
+
return this.getCards({ sort: options.sort }).map((card) => card.id);
|
|
412
|
+
},
|
|
413
|
+
getSelectedCardIds() {
|
|
414
|
+
return this.getCards().filter((card) => card.getIsSelected()).map((card) => card.id);
|
|
415
|
+
},
|
|
416
|
+
getSelectableCardIds() {
|
|
417
|
+
return this.getCards().filter((card) => card.getCanSelect()).map((card) => card.id);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/headless/features/board.ts
|
|
424
|
+
function squareGeometry(board, size, origin) {
|
|
425
|
+
const spaces = Object.values(board.spaces).map((space) => {
|
|
426
|
+
const x2 = origin.x + space.col * size;
|
|
427
|
+
const y2 = origin.y + space.row * size;
|
|
428
|
+
return {
|
|
429
|
+
id: space.id,
|
|
430
|
+
center: { x: x2 + size / 2, y: y2 + size / 2 },
|
|
431
|
+
corners: [
|
|
432
|
+
{ x: x2, y: y2 },
|
|
433
|
+
{ x: x2 + size, y: y2 },
|
|
434
|
+
{ x: x2 + size, y: y2 + size },
|
|
435
|
+
{ x: x2, y: y2 + size }
|
|
436
|
+
]
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
const byId = new Map(spaces.map((space) => [space.id, space]));
|
|
440
|
+
function sharedCorners(ids) {
|
|
441
|
+
const first = byId.get(ids[0] ?? "")?.corners ?? [];
|
|
442
|
+
return first.filter(
|
|
443
|
+
(point) => ids.every(
|
|
444
|
+
(id) => byId.get(id)?.corners.some(
|
|
445
|
+
(candidate) => candidate.x === point.x && candidate.y === point.y
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
const points = spaces.flatMap((space) => space.corners);
|
|
451
|
+
const xs = points.map((point) => point.x);
|
|
452
|
+
const ys = points.map((point) => point.y);
|
|
453
|
+
const x = xs.length ? Math.min(...xs) : 0;
|
|
454
|
+
const y = ys.length ? Math.min(...ys) : 0;
|
|
455
|
+
return {
|
|
456
|
+
viewBox: {
|
|
457
|
+
x,
|
|
458
|
+
y,
|
|
459
|
+
width: xs.length ? Math.max(...xs) - x : 0,
|
|
460
|
+
height: ys.length ? Math.max(...ys) - y : 0
|
|
461
|
+
},
|
|
462
|
+
spaces,
|
|
463
|
+
// Incidence is authored. Only a unique geometric line/point is representable.
|
|
464
|
+
edges: board.edges.flatMap((edge) => {
|
|
465
|
+
const corners = sharedCorners(edge.spaceIds);
|
|
466
|
+
return corners.length === 2 ? [{ id: edge.id, from: corners[0], to: corners[1] }] : [];
|
|
467
|
+
}),
|
|
468
|
+
vertices: board.vertices.flatMap((vertex) => {
|
|
469
|
+
const corners = sharedCorners(vertex.spaceIds);
|
|
470
|
+
return corners.length === 1 ? [{ id: vertex.id, center: corners[0] }] : [];
|
|
471
|
+
}),
|
|
472
|
+
pointToSpace(point) {
|
|
473
|
+
const col = Math.floor((point.x - origin.x) / size);
|
|
474
|
+
const row = Math.floor((point.y - origin.y) / size);
|
|
475
|
+
return Object.values(board.spaces).find(
|
|
476
|
+
(space) => space.col === col && space.row === row
|
|
477
|
+
)?.id;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function boardFeature(game, context) {
|
|
482
|
+
const captures = /* @__PURE__ */ new WeakMap();
|
|
483
|
+
const geometries = /* @__PURE__ */ new WeakMap();
|
|
484
|
+
let cached;
|
|
485
|
+
function collection() {
|
|
486
|
+
const model = game.getSnapshot();
|
|
487
|
+
const source = game.getOptions().source;
|
|
488
|
+
if (cached?.view === model.view && cached.interactions === model.interactions && cached.source === source)
|
|
489
|
+
return cached.collection;
|
|
490
|
+
const projected = model.view;
|
|
491
|
+
const boards = Object.values(projected?.boards?.byId ?? {}).map((data) => {
|
|
492
|
+
const board = context.createBoard(
|
|
493
|
+
data.id,
|
|
494
|
+
data
|
|
495
|
+
);
|
|
496
|
+
let geometry = geometries.get(data);
|
|
497
|
+
if (!geometry && data.layout === "hex") {
|
|
498
|
+
geometry = createHexBoardGeometry({
|
|
499
|
+
id: data.baseId ?? data.id,
|
|
500
|
+
orientation: data.orientation,
|
|
501
|
+
spaces: Object.values(data.spaces)
|
|
502
|
+
});
|
|
503
|
+
geometries.set(data, geometry);
|
|
504
|
+
}
|
|
505
|
+
captures.set(board, { model, source, geometry });
|
|
506
|
+
return board;
|
|
507
|
+
});
|
|
508
|
+
const byId = new Map(boards.map((board) => [board.id, board]));
|
|
509
|
+
const result = Object.freeze({
|
|
510
|
+
get(id) {
|
|
511
|
+
return requireLookup(byId.get(id), "Board", id);
|
|
512
|
+
},
|
|
513
|
+
find(id) {
|
|
514
|
+
return byId.get(id);
|
|
515
|
+
},
|
|
516
|
+
getAll: () => Object.freeze(boards)
|
|
517
|
+
});
|
|
518
|
+
cached = {
|
|
519
|
+
view: model.view,
|
|
520
|
+
interactions: model.interactions,
|
|
521
|
+
source,
|
|
522
|
+
collection: result
|
|
523
|
+
};
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
function targetsFor(owner) {
|
|
527
|
+
const board = owner.data;
|
|
528
|
+
const captured = captures.get(owner);
|
|
529
|
+
function matchingTargets(model, kind, id) {
|
|
530
|
+
const inputKinds = kind === "space" ? ["space", "tile"] : [kind];
|
|
531
|
+
return model.interactions.list().flatMap(
|
|
532
|
+
(interaction) => interaction.getInputs().filter((input) => {
|
|
533
|
+
const domain = input.getDomain();
|
|
534
|
+
return domain.type === "boardTarget" && inputKinds.includes(String(domain.targetKind)) && (domain.valueKind === "player-board-space" ? board.scope === "perPlayer" && domain.boardId === board.baseId : domain.boardId === board.id);
|
|
535
|
+
}).map((input) => {
|
|
536
|
+
const domain = input.getDomain();
|
|
537
|
+
return {
|
|
538
|
+
interaction,
|
|
539
|
+
input,
|
|
540
|
+
value: domain.type === "boardTarget" && domain.valueKind === "player-board-space" ? Object.freeze({
|
|
541
|
+
boardId: board.baseId,
|
|
542
|
+
playerId: board.playerId,
|
|
543
|
+
spaceId: id
|
|
544
|
+
}) : id
|
|
545
|
+
};
|
|
546
|
+
})
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
function target(kind, id) {
|
|
550
|
+
const matches = matchingTargets(captured.model, kind, id);
|
|
551
|
+
const eligible = matches.some(
|
|
552
|
+
({ input, value }) => input.getIsEligible(value)
|
|
553
|
+
);
|
|
554
|
+
const selected = matches.some(
|
|
555
|
+
({ input, value }) => input.getIsSelected(value)
|
|
556
|
+
);
|
|
557
|
+
const selectable = matches.some(
|
|
558
|
+
({ input, value }) => !input.getTargetProps(value).disabled
|
|
559
|
+
);
|
|
560
|
+
function select(options) {
|
|
561
|
+
if (game.getOptions().source !== captured.source || game.snapshot?.me !== captured.model.snapshot?.me)
|
|
562
|
+
return;
|
|
563
|
+
const matching = matchingTargets(game.getSnapshot(), kind, id).filter(
|
|
564
|
+
({ interaction, input, value: value2 }) => (!options?.interaction || options.interaction === interaction.key) && (!options?.input || options.input === input.key) && !input.getTargetProps(value2).disabled
|
|
565
|
+
);
|
|
566
|
+
if (!matching.length) return;
|
|
567
|
+
if (matching.length > 1) throw new AmbiguousTargetError(id);
|
|
568
|
+
const domain = matching[0].input.getDomain();
|
|
569
|
+
if (domain.type !== "boardTarget") return;
|
|
570
|
+
const value = matching[0].value;
|
|
571
|
+
const target2 = domain.valueKind === "player-board-space" && typeof value !== "string" ? { kind: "space", valueKind: domain.valueKind, value } : {
|
|
572
|
+
kind,
|
|
573
|
+
valueKind: "board-id",
|
|
574
|
+
value: id,
|
|
575
|
+
boardId: board.id
|
|
576
|
+
};
|
|
577
|
+
context.routeTarget(target2, {
|
|
578
|
+
interaction: matching[0].interaction.key,
|
|
579
|
+
input: matching[0].input.key
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
id,
|
|
584
|
+
getIsEligible: () => eligible,
|
|
585
|
+
getIsSelectable: () => selectable,
|
|
586
|
+
getIsSelected: () => selected,
|
|
587
|
+
getSelectHandler: (options) => () => select(options),
|
|
588
|
+
getTargetProps(options) {
|
|
589
|
+
const candidates = matches.filter(
|
|
590
|
+
({ interaction, input, value }) => (!options?.interaction || interaction.key === options.interaction) && (!options?.input || options.input === input.key) && !input.getTargetProps(value).disabled
|
|
591
|
+
);
|
|
592
|
+
const only = candidates.length === 1 ? candidates[0] : void 0;
|
|
593
|
+
return {
|
|
594
|
+
type: "button",
|
|
595
|
+
disabled: candidates.length === 0,
|
|
596
|
+
"data-action": "select",
|
|
597
|
+
"data-value": only && typeof only.value !== "string" ? JSON.stringify(only.value) : id,
|
|
598
|
+
"data-board": board.id,
|
|
599
|
+
"data-interaction": options?.interaction ?? only?.interaction.key,
|
|
600
|
+
"data-input": only?.input.key,
|
|
601
|
+
"data-eligible": eligible,
|
|
602
|
+
"data-selected": selected,
|
|
603
|
+
"data-disabled": candidates.length === 0,
|
|
604
|
+
onClick: () => select(options)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return target;
|
|
610
|
+
}
|
|
611
|
+
const spaceCollections = /* @__PURE__ */ new WeakMap();
|
|
612
|
+
function spacesFor(owner) {
|
|
613
|
+
const cached2 = spaceCollections.get(owner);
|
|
614
|
+
if (cached2) return cached2;
|
|
615
|
+
const target = targetsFor(owner);
|
|
616
|
+
const data = owner.data;
|
|
617
|
+
const values = Object.freeze(
|
|
618
|
+
Object.values(data.spaces).map(
|
|
619
|
+
(space) => Object.freeze({
|
|
620
|
+
...target("space", space.id),
|
|
621
|
+
data: space,
|
|
622
|
+
board: owner
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
const byId = new Map(values.map((space) => [space.id, space]));
|
|
627
|
+
const spaces = Object.freeze({
|
|
628
|
+
get: (id) => requireLookup(byId.get(id), `Space on board ${owner.id}`, id),
|
|
629
|
+
find: (id) => byId.get(id),
|
|
630
|
+
getAll: () => values
|
|
631
|
+
});
|
|
632
|
+
spaceCollections.set(owner, spaces);
|
|
633
|
+
return spaces;
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
root: {
|
|
637
|
+
get boards() {
|
|
638
|
+
return collection();
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
board: {
|
|
642
|
+
get spaces() {
|
|
643
|
+
return spacesFor(this);
|
|
644
|
+
},
|
|
645
|
+
getLayout(options) {
|
|
646
|
+
const board = this.data;
|
|
647
|
+
if (board.layout === "generic")
|
|
648
|
+
throw new Error(
|
|
649
|
+
`Board '${board.id}' has no spatial geometry; use board.spaces for semantic selection.`
|
|
650
|
+
);
|
|
651
|
+
const {
|
|
652
|
+
hexSize,
|
|
653
|
+
origin: suppliedOrigin = { x: 0, y: 0 },
|
|
654
|
+
viewport: suppliedViewport = { x: 0, y: 0, scale: 1 }
|
|
655
|
+
} = options;
|
|
656
|
+
const origin = { ...suppliedOrigin };
|
|
657
|
+
const viewport = { ...suppliedViewport };
|
|
658
|
+
if (!Number.isFinite(hexSize) || hexSize <= 0)
|
|
659
|
+
throw new Error("hexSize must be positive and finite.");
|
|
660
|
+
if (![origin.x, origin.y, viewport.x, viewport.y, viewport.scale].every(
|
|
661
|
+
Number.isFinite
|
|
662
|
+
) || viewport.scale <= 0)
|
|
663
|
+
throw new Error(
|
|
664
|
+
"Layout transform must be finite with positive scale."
|
|
665
|
+
);
|
|
666
|
+
const captured = captures.get(this);
|
|
667
|
+
const geometry = board.layout === "hex" ? captured.geometry.getLayout({ hexSize, origin }) : squareGeometry(board, hexSize, origin);
|
|
668
|
+
const point = (value) => Object.freeze({
|
|
669
|
+
x: value.x * viewport.scale + viewport.x,
|
|
670
|
+
y: value.y * viewport.scale + viewport.y
|
|
671
|
+
});
|
|
672
|
+
const target = targetsFor(this);
|
|
673
|
+
const semanticSpaces = spacesFor(this);
|
|
674
|
+
const spaces = Object.freeze(
|
|
675
|
+
geometry.spaces.map((space) => {
|
|
676
|
+
const corners = Object.freeze(space.corners.map(point));
|
|
677
|
+
const center = point(space.center);
|
|
678
|
+
return Object.freeze({
|
|
679
|
+
...semanticSpaces.get(
|
|
680
|
+
space.id
|
|
681
|
+
),
|
|
682
|
+
center,
|
|
683
|
+
points: () => corners,
|
|
684
|
+
transform: `translate(${center.x} ${center.y})`
|
|
685
|
+
});
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
const edges = Object.freeze(
|
|
689
|
+
geometry.edges.map(
|
|
690
|
+
(edge) => Object.freeze({
|
|
691
|
+
...target("edge", edge.id),
|
|
692
|
+
data: board.edges.find((data) => data.id === edge.id),
|
|
693
|
+
center: point({
|
|
694
|
+
x: (edge.from.x + edge.to.x) / 2,
|
|
695
|
+
y: (edge.from.y + edge.to.y) / 2
|
|
696
|
+
}),
|
|
697
|
+
line: Object.freeze([point(edge.from), point(edge.to)])
|
|
698
|
+
})
|
|
699
|
+
)
|
|
700
|
+
);
|
|
701
|
+
const vertices = Object.freeze(
|
|
702
|
+
geometry.vertices.map(
|
|
703
|
+
(vertex) => Object.freeze({
|
|
704
|
+
...target("vertex", vertex.id),
|
|
705
|
+
data: board.vertices.find((data) => data.id === vertex.id),
|
|
706
|
+
center: point(vertex.center)
|
|
707
|
+
})
|
|
708
|
+
)
|
|
709
|
+
);
|
|
710
|
+
return Object.freeze({
|
|
711
|
+
viewBox: Object.freeze({ ...geometry.viewBox }),
|
|
712
|
+
getSpaces: () => spaces,
|
|
713
|
+
getEdges: () => edges,
|
|
714
|
+
getVertices: () => vertices,
|
|
715
|
+
pointToSpace(x, y) {
|
|
716
|
+
return geometry.pointToSpace({
|
|
717
|
+
x: (x - viewport.x) / viewport.scale,
|
|
718
|
+
y: (y - viewport.y) / viewport.scale
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/headless/features/drag.ts
|
|
728
|
+
function dragFeature(game, context) {
|
|
729
|
+
let active = null;
|
|
730
|
+
let selection;
|
|
731
|
+
let source = game.getOptions().source;
|
|
732
|
+
let seat = game.snapshot?.me;
|
|
733
|
+
let disposed = false;
|
|
734
|
+
let version = game.snapshot?.version;
|
|
735
|
+
let lastInteractions = game.interactions;
|
|
736
|
+
let lastCards = game.cards;
|
|
737
|
+
let branch = snapshot();
|
|
738
|
+
function update(next) {
|
|
739
|
+
if (disposed) return;
|
|
740
|
+
active = next === null ? null : Object.freeze(next);
|
|
741
|
+
branch = snapshot();
|
|
742
|
+
context.invalidate();
|
|
743
|
+
}
|
|
744
|
+
function targets(cardId = active?.cardId, selected = selection) {
|
|
745
|
+
if (!cardId || game.connection !== "ready") return [];
|
|
746
|
+
const runtime = game;
|
|
747
|
+
const options = selected;
|
|
748
|
+
const resolved = (runtime.cards.find(cardId)?.getInteractions() ?? []).flatMap((candidate) => {
|
|
749
|
+
if (!candidate.getIsAvailable() || options?.interaction && candidate.key !== options.interaction)
|
|
750
|
+
return [];
|
|
751
|
+
const inputs = candidate.getInputs();
|
|
752
|
+
const cardInputs = inputs.filter(
|
|
753
|
+
(input) => input.getDomain().type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(cardId)
|
|
754
|
+
);
|
|
755
|
+
return cardInputs.flatMap(
|
|
756
|
+
(cardInput) => inputs.flatMap((input) => {
|
|
757
|
+
const domain = input.getDomain();
|
|
758
|
+
if (domain.type !== "boardTarget") return [];
|
|
759
|
+
const route = {
|
|
760
|
+
interactionKey: candidate.key,
|
|
761
|
+
cardInputKey: cardInput.key,
|
|
762
|
+
inputKey: input.key
|
|
763
|
+
};
|
|
764
|
+
if (domain.valueKind === "player-board-space")
|
|
765
|
+
return domain.eligibleTargets.filter((value) => !input.getTargetProps(value).disabled).map(
|
|
766
|
+
(value) => Object.freeze({
|
|
767
|
+
kind: "space",
|
|
768
|
+
valueKind: domain.valueKind,
|
|
769
|
+
value,
|
|
770
|
+
...route
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
return domain.eligibleTargets.filter((value) => !input.getTargetProps(value).disabled).map(
|
|
774
|
+
(value) => Object.freeze({
|
|
775
|
+
kind: domain.targetKind,
|
|
776
|
+
valueKind: domain.valueKind,
|
|
777
|
+
value,
|
|
778
|
+
boardId: domain.boardId,
|
|
779
|
+
...route
|
|
780
|
+
})
|
|
781
|
+
);
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
return resolved;
|
|
786
|
+
}
|
|
787
|
+
function sameTarget(left, right) {
|
|
788
|
+
return left.kind === right.kind && left.valueKind === right.valueKind && inputValueKey(left.value) === inputValueKey(right.value) && (left.valueKind !== "board-id" || right.valueKind === "board-id" && left.boardId === right.boardId) && left.interactionKey === right.interactionKey && left.cardInputKey === right.cardInputKey && left.inputKey === right.inputKey;
|
|
789
|
+
}
|
|
790
|
+
function cancel() {
|
|
791
|
+
if (active) update(null);
|
|
792
|
+
}
|
|
793
|
+
function snapshot() {
|
|
794
|
+
const captured = active;
|
|
795
|
+
const dropTargets = Object.freeze(targets());
|
|
796
|
+
return Object.freeze({
|
|
797
|
+
active: captured,
|
|
798
|
+
getCanDrag: (cardId, options) => !disposed && targets(cardId, options).length > 0,
|
|
799
|
+
begin(cardId, options) {
|
|
800
|
+
if (disposed || !targets(cardId, options).length) return false;
|
|
801
|
+
selection = options;
|
|
802
|
+
update({ cardId, target: null });
|
|
803
|
+
return true;
|
|
804
|
+
},
|
|
805
|
+
getDropTargets: () => dropTargets,
|
|
806
|
+
setDropTarget(target) {
|
|
807
|
+
if (!active || disposed) return;
|
|
808
|
+
if (target && !targets().some((candidate) => sameTarget(candidate, target))) {
|
|
809
|
+
update({ ...active, target: null });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
update({ ...active, target });
|
|
813
|
+
},
|
|
814
|
+
drop() {
|
|
815
|
+
const finished = active;
|
|
816
|
+
const eligible = finished?.target && targets().some((target) => sameTarget(target, finished.target));
|
|
817
|
+
cancel();
|
|
818
|
+
if (!disposed && finished?.target && eligible)
|
|
819
|
+
context.routeCardDrop(finished.cardId, finished.target);
|
|
820
|
+
},
|
|
821
|
+
cancel
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
const unsubscribe = game.subscribe(() => {
|
|
825
|
+
const nextSource = game.getOptions().source;
|
|
826
|
+
const nextSeat = game.snapshot?.me;
|
|
827
|
+
const nextVersion = game.snapshot?.version;
|
|
828
|
+
if (nextSource !== source || nextSeat !== seat || nextVersion !== version || game.connection !== "ready") {
|
|
829
|
+
source = nextSource;
|
|
830
|
+
seat = nextSeat;
|
|
831
|
+
version = nextVersion;
|
|
832
|
+
cancel();
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
root: {
|
|
837
|
+
get drag() {
|
|
838
|
+
if (lastInteractions !== game.interactions || lastCards !== game.cards) {
|
|
839
|
+
lastInteractions = game.interactions;
|
|
840
|
+
lastCards = game.cards;
|
|
841
|
+
branch = snapshot();
|
|
842
|
+
}
|
|
843
|
+
return branch;
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
dispose() {
|
|
847
|
+
unsubscribe();
|
|
848
|
+
cancel();
|
|
849
|
+
disposed = true;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/headless/features/pointer-session.ts
|
|
855
|
+
function createPointerSession(callbacks) {
|
|
856
|
+
let disposed = false;
|
|
857
|
+
let active;
|
|
858
|
+
function value() {
|
|
859
|
+
const { origin, point } = active;
|
|
860
|
+
return {
|
|
861
|
+
origin,
|
|
862
|
+
point,
|
|
863
|
+
delta: { x: point.x - origin.x, y: point.y - origin.y }
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function release() {
|
|
867
|
+
const previous = active;
|
|
868
|
+
active = void 0;
|
|
869
|
+
if (previous?.surface.hasPointerCapture(previous.id)) {
|
|
870
|
+
previous.surface.releasePointerCapture(previous.id);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function cancel() {
|
|
874
|
+
if (!active) return;
|
|
875
|
+
release();
|
|
876
|
+
callbacks.cancel();
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
start(event) {
|
|
880
|
+
if (disposed || active || event.button !== 0) return false;
|
|
881
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
882
|
+
const point = { x: event.clientX, y: event.clientY };
|
|
883
|
+
active = {
|
|
884
|
+
id: event.pointerId,
|
|
885
|
+
surface: event.currentTarget,
|
|
886
|
+
origin: point,
|
|
887
|
+
point
|
|
888
|
+
};
|
|
889
|
+
event.preventDefault();
|
|
890
|
+
return true;
|
|
891
|
+
},
|
|
892
|
+
move(event) {
|
|
893
|
+
if (event.pointerId !== active?.id) return;
|
|
894
|
+
active.point = { x: event.clientX, y: event.clientY };
|
|
895
|
+
event.preventDefault();
|
|
896
|
+
callbacks.move(value());
|
|
897
|
+
},
|
|
898
|
+
end(event) {
|
|
899
|
+
if (event.pointerId !== active?.id) return;
|
|
900
|
+
active.point = { x: event.clientX, y: event.clientY };
|
|
901
|
+
const result = value();
|
|
902
|
+
release();
|
|
903
|
+
callbacks.end(result);
|
|
904
|
+
},
|
|
905
|
+
cancel(event) {
|
|
906
|
+
if (event && event.pointerId !== active?.id) return;
|
|
907
|
+
cancel();
|
|
908
|
+
},
|
|
909
|
+
dispose() {
|
|
910
|
+
disposed = true;
|
|
911
|
+
cancel();
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/headless/features/pan-zoom.ts
|
|
917
|
+
function panZoomFeature(game, context, options = {}) {
|
|
918
|
+
const minScale = options.minScale ?? 0.25;
|
|
919
|
+
const maxScale = options.maxScale ?? 4;
|
|
920
|
+
if (!Number.isFinite(minScale) || !Number.isFinite(maxScale) || minScale <= 0 || maxScale < minScale) {
|
|
921
|
+
throw new Error(
|
|
922
|
+
"Viewport scale limits must be positive, finite, and ordered."
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const initial = options.initial ?? { x: 0, y: 0, scale: 1 };
|
|
926
|
+
let disposed = false;
|
|
927
|
+
let source = game.getOptions().source;
|
|
928
|
+
let seat = game.snapshot?.me;
|
|
929
|
+
let dragOrigin = initial;
|
|
930
|
+
let transform = admit(initial);
|
|
931
|
+
let branch = snapshot();
|
|
932
|
+
function admit(value) {
|
|
933
|
+
if (![value.x, value.y, value.scale].every(Number.isFinite) || value.scale <= 0) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
"Viewport transform must have finite coordinates and positive scale."
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
return Object.freeze({
|
|
939
|
+
x: value.x,
|
|
940
|
+
y: value.y,
|
|
941
|
+
scale: Math.min(maxScale, Math.max(minScale, value.scale))
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function update(value) {
|
|
945
|
+
if (disposed) return;
|
|
946
|
+
const next = admit(value);
|
|
947
|
+
if (next.x === transform.x && next.y === transform.y && next.scale === transform.scale)
|
|
948
|
+
return;
|
|
949
|
+
transform = next;
|
|
950
|
+
branch = snapshot();
|
|
951
|
+
context.invalidate();
|
|
952
|
+
}
|
|
953
|
+
const pointer = createPointerSession({
|
|
954
|
+
move: ({ delta }) => update({
|
|
955
|
+
...dragOrigin,
|
|
956
|
+
x: dragOrigin.x + delta.x,
|
|
957
|
+
y: dragOrigin.y + delta.y
|
|
958
|
+
}),
|
|
959
|
+
end: ({ delta }) => update({
|
|
960
|
+
...dragOrigin,
|
|
961
|
+
x: dragOrigin.x + delta.x,
|
|
962
|
+
y: dragOrigin.y + delta.y
|
|
963
|
+
}),
|
|
964
|
+
cancel() {
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
function props() {
|
|
968
|
+
return {
|
|
969
|
+
style: { touchAction: "none" },
|
|
970
|
+
onPointerDown(event) {
|
|
971
|
+
if (pointer.start(event)) dragOrigin = transform;
|
|
972
|
+
},
|
|
973
|
+
onPointerMove: pointer.move,
|
|
974
|
+
onPointerUp: pointer.end,
|
|
975
|
+
onPointerCancel: pointer.cancel,
|
|
976
|
+
onLostPointerCapture: pointer.cancel,
|
|
977
|
+
onWheel(event) {
|
|
978
|
+
if (disposed) return;
|
|
979
|
+
event.preventDefault();
|
|
980
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
981
|
+
const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? rect.height : 1;
|
|
982
|
+
const scale = Math.min(
|
|
983
|
+
maxScale,
|
|
984
|
+
Math.max(
|
|
985
|
+
minScale,
|
|
986
|
+
transform.scale * Math.exp(-event.deltaY * unit * 2e-3)
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
const x = event.clientX - rect.left;
|
|
990
|
+
const y = event.clientY - rect.top;
|
|
991
|
+
const ratio = scale / transform.scale;
|
|
992
|
+
update({
|
|
993
|
+
x: x - (x - transform.x) * ratio,
|
|
994
|
+
y: y - (y - transform.y) * ratio,
|
|
995
|
+
scale
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function snapshot() {
|
|
1001
|
+
const captured = transform;
|
|
1002
|
+
return Object.freeze({
|
|
1003
|
+
transform: captured,
|
|
1004
|
+
getTransform: () => captured,
|
|
1005
|
+
getProps: props,
|
|
1006
|
+
setTransform: update,
|
|
1007
|
+
reset: () => {
|
|
1008
|
+
pointer.cancel();
|
|
1009
|
+
update(initial);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
const unsubscribe = game.subscribe(() => {
|
|
1014
|
+
const nextSource = game.getOptions().source;
|
|
1015
|
+
const nextSeat = game.snapshot?.me;
|
|
1016
|
+
if (nextSource !== source || nextSeat !== seat) {
|
|
1017
|
+
source = nextSource;
|
|
1018
|
+
seat = nextSeat;
|
|
1019
|
+
pointer.cancel();
|
|
1020
|
+
update(initial);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
return {
|
|
1024
|
+
root: {
|
|
1025
|
+
get viewport() {
|
|
1026
|
+
return branch;
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
dispose() {
|
|
1030
|
+
disposed = true;
|
|
1031
|
+
unsubscribe();
|
|
1032
|
+
pointer.dispose();
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
7
1036
|
export {
|
|
1037
|
+
AmbiguousTargetError,
|
|
1038
|
+
AuthAcceptedFrameSchema,
|
|
1039
|
+
AuthConnectFrameSchema,
|
|
1040
|
+
AuthRefreshFrameSchema,
|
|
1041
|
+
BoardStaticProjectionSchema,
|
|
1042
|
+
CancelInteractionCommandSchema,
|
|
1043
|
+
ClientGameplayFrameSchema,
|
|
1044
|
+
DREAMBOARD_PLUGIN_PROTOCOL,
|
|
1045
|
+
DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
|
|
8
1046
|
DREAMBOARD_SDK_PACKAGES,
|
|
9
1047
|
DREAMBOARD_SDK_PACKAGE_SET,
|
|
10
|
-
DREAMBOARD_SDK_VERSION
|
|
1048
|
+
DREAMBOARD_SDK_VERSION,
|
|
1049
|
+
GameEventDetailSchema,
|
|
1050
|
+
GameEventSchema,
|
|
1051
|
+
GameOutcomeSchema,
|
|
1052
|
+
GameplayBackpressureFrameSchema,
|
|
1053
|
+
GameplayBackpressureOperationSchema,
|
|
1054
|
+
GameplayBackpressureReasonSchema,
|
|
1055
|
+
GameplayBasisSchema,
|
|
1056
|
+
GameplayCloseCode,
|
|
1057
|
+
GameplayCredentialSchema,
|
|
1058
|
+
HostToPluginEnvelopeSchema,
|
|
1059
|
+
HostToPluginPayloadSchema,
|
|
1060
|
+
InputDomainSchema,
|
|
1061
|
+
InteractionAvailabilitySchema,
|
|
1062
|
+
InteractionCommitPolicySchema,
|
|
1063
|
+
InteractionDescriptorSchema,
|
|
1064
|
+
InteractionInputDescriptorSchema,
|
|
1065
|
+
InteractionResultSchema,
|
|
1066
|
+
PluginGameplayFrameSchema,
|
|
1067
|
+
PluginPlayerSummarySchema,
|
|
1068
|
+
PluginSessionDescriptorSchema,
|
|
1069
|
+
PluginToHostEnvelopeSchema,
|
|
1070
|
+
PluginToHostPayloadSchema,
|
|
1071
|
+
RuntimeJsonSchema,
|
|
1072
|
+
SeatFrameSchema,
|
|
1073
|
+
SeatProjectionBundleSchema,
|
|
1074
|
+
ServerGameplayFrameSchema,
|
|
1075
|
+
SessionResumeFrameSchema,
|
|
1076
|
+
SessionSnapshotFrameSchema,
|
|
1077
|
+
SimultaneousPhaseSnapshotSchema,
|
|
1078
|
+
SubmitInteractionCommandSchema,
|
|
1079
|
+
SystemActionEventSchema,
|
|
1080
|
+
ZoneHandlesSnapshotSchema,
|
|
1081
|
+
boardFeature,
|
|
1082
|
+
canonicalizePluginRuntimeJson,
|
|
1083
|
+
computePluginActionSetVersion,
|
|
1084
|
+
createGameInstance,
|
|
1085
|
+
createPluginProtocolEnvelopeSchema,
|
|
1086
|
+
digestPluginCommandRequest,
|
|
1087
|
+
digestPluginGameplayFrame,
|
|
1088
|
+
digestPluginRuntimeJson,
|
|
1089
|
+
dragFeature,
|
|
1090
|
+
encodeCanonicalPluginRuntimeJson,
|
|
1091
|
+
handFeature,
|
|
1092
|
+
hostSource,
|
|
1093
|
+
iframeSource,
|
|
1094
|
+
materializePluginGameplayFrame,
|
|
1095
|
+
panZoomFeature,
|
|
1096
|
+
parseHostToPluginEnvelope,
|
|
1097
|
+
parsePluginGameplayFrame,
|
|
1098
|
+
parsePluginSessionDescriptor,
|
|
1099
|
+
parsePluginToHostEnvelope,
|
|
1100
|
+
staticSource
|
|
11
1101
|
};
|
|
12
1102
|
//# sourceMappingURL=index.js.map
|