@dreamboard-games/sdk 0.5.0-alpha.2 → 0.5.0-alpha.21

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 (150) hide show
  1. package/README.md +462 -53
  2. package/dist/board-element-CqtxU5VZ.d.ts +13 -0
  3. package/dist/chunk-3RFGPBES.js +1792 -0
  4. package/dist/chunk-3RFGPBES.js.map +1 -0
  5. package/dist/chunk-CZRPUWCT.js +1602 -0
  6. package/dist/chunk-CZRPUWCT.js.map +1 -0
  7. package/dist/chunk-DMSXVMYY.js +7199 -0
  8. package/dist/chunk-DMSXVMYY.js.map +1 -0
  9. package/dist/chunk-U6R7FAH4.js +630 -0
  10. package/dist/chunk-U6R7FAH4.js.map +1 -0
  11. package/dist/chunk-YT3RPO5J.js +635 -0
  12. package/dist/chunk-YT3RPO5J.js.map +1 -0
  13. package/dist/diagnostics-ZAPEBlJP.d.ts +64 -0
  14. package/dist/extract-CwJWDrSD.d.ts +6850 -0
  15. package/dist/index.d.ts +5177 -1
  16. package/dist/index.js +1990 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/react.d.ts +158 -0
  19. package/dist/react.js +1119 -0
  20. package/dist/react.js.map +1 -0
  21. package/dist/reducer.d.ts +1028 -97
  22. package/dist/reducer.js +4892 -125
  23. package/dist/reducer.js.map +1 -1
  24. package/dist/shortcuts-B_RrufOM.d.ts +822 -0
  25. package/dist/testing.d.ts +529 -569
  26. package/dist/testing.js +2588 -934
  27. package/dist/testing.js.map +1 -1
  28. package/dist/types-BxXRINRF.d.ts +118 -0
  29. package/package.json +39 -144
  30. package/dist/HandView-Bi2lxzQP.d.ts +0 -79
  31. package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
  32. package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
  33. package/dist/attributes-DbvyMbXw.d.ts +0 -68
  34. package/dist/browser-interaction.d.ts +0 -910
  35. package/dist/browser-interaction.js +0 -119
  36. package/dist/browser-interaction.js.map +0 -1
  37. package/dist/chunk-32PFKDV7.js +0 -11705
  38. package/dist/chunk-32PFKDV7.js.map +0 -1
  39. package/dist/chunk-3BAD4VC2.js +0 -263
  40. package/dist/chunk-3BAD4VC2.js.map +0 -1
  41. package/dist/chunk-3SKDNDPG.js +0 -104
  42. package/dist/chunk-3SKDNDPG.js.map +0 -1
  43. package/dist/chunk-5YV2WHJ4.js +0 -25
  44. package/dist/chunk-5YV2WHJ4.js.map +0 -1
  45. package/dist/chunk-ANRB4XKA.js +0 -90
  46. package/dist/chunk-ANRB4XKA.js.map +0 -1
  47. package/dist/chunk-BI4G3PM2.js +0 -1864
  48. package/dist/chunk-BI4G3PM2.js.map +0 -1
  49. package/dist/chunk-EVCLTY4Q.js +0 -935
  50. package/dist/chunk-EVCLTY4Q.js.map +0 -1
  51. package/dist/chunk-GDPKTBUR.js +0 -564
  52. package/dist/chunk-GDPKTBUR.js.map +0 -1
  53. package/dist/chunk-H6VDGFL5.js +0 -2903
  54. package/dist/chunk-H6VDGFL5.js.map +0 -1
  55. package/dist/chunk-LR3ZTWQF.js +0 -5960
  56. package/dist/chunk-LR3ZTWQF.js.map +0 -1
  57. package/dist/chunk-PF7L4BMG.js +0 -117
  58. package/dist/chunk-PF7L4BMG.js.map +0 -1
  59. package/dist/chunk-PN5O6GG2.js +0 -32
  60. package/dist/chunk-PN5O6GG2.js.map +0 -1
  61. package/dist/chunk-PZ5AY32C.js +0 -10
  62. package/dist/chunk-PZ5AY32C.js.map +0 -1
  63. package/dist/chunk-S2JEDVWH.js +0 -7165
  64. package/dist/chunk-S2JEDVWH.js.map +0 -1
  65. package/dist/chunk-T3ZKNUZ7.js +0 -1
  66. package/dist/chunk-T3ZKNUZ7.js.map +0 -1
  67. package/dist/chunk-UGH54WZ2.js +0 -223
  68. package/dist/chunk-UGH54WZ2.js.map +0 -1
  69. package/dist/chunk-VCDWP5VL.js +0 -746
  70. package/dist/chunk-VCDWP5VL.js.map +0 -1
  71. package/dist/chunk-VDXOF4FW.js +0 -69
  72. package/dist/chunk-VDXOF4FW.js.map +0 -1
  73. package/dist/chunk-WHR5UW3F.js +0 -1988
  74. package/dist/chunk-WHR5UW3F.js.map +0 -1
  75. package/dist/chunk-Y75CFE77.js +0 -17
  76. package/dist/chunk-Y75CFE77.js.map +0 -1
  77. package/dist/chunk-Z7QCREWI.js +0 -3435
  78. package/dist/chunk-Z7QCREWI.js.map +0 -1
  79. package/dist/chunk-ZZGN2ATS.js +0 -1941
  80. package/dist/chunk-ZZGN2ATS.js.map +0 -1
  81. package/dist/components-gAbIz7hK.d.ts +0 -1284
  82. package/dist/definitions-BgmV_GhC.d.ts +0 -300
  83. package/dist/diagnostics-DIi3ina7.d.ts +0 -50
  84. package/dist/digest.d.ts +0 -8
  85. package/dist/game-CsZScpcn.d.ts +0 -763
  86. package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
  87. package/dist/index-CcT9Q7N7.d.ts +0 -193
  88. package/dist/index.d-DjzoK7zn.d.ts +0 -2101
  89. package/dist/package-set.d.ts +0 -13
  90. package/dist/package-set.js +0 -12
  91. package/dist/package-set.js.map +0 -1
  92. package/dist/player-state-Cqpyeql0.d.ts +0 -371
  93. package/dist/plugin-runtime-contract.d.ts +0 -17
  94. package/dist/plugin-runtime-contract.js +0 -92
  95. package/dist/plugin-runtime-contract.js.map +0 -1
  96. package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
  97. package/dist/protocol-dYgafTYY.d.ts +0 -314
  98. package/dist/reducer/advanced.d.ts +0 -82
  99. package/dist/reducer/advanced.js +0 -51
  100. package/dist/reducer/advanced.js.map +0 -1
  101. package/dist/reducer-contract.d.ts +0 -11
  102. package/dist/reducer-contract.js +0 -16
  103. package/dist/reducer-contract.js.map +0 -1
  104. package/dist/reference-games/index.d.ts +0 -31
  105. package/dist/reference-games/index.js +0 -48
  106. package/dist/reference-games/index.js.map +0 -1
  107. package/dist/runtime/primitives.d.ts +0 -250
  108. package/dist/runtime/primitives.js +0 -189
  109. package/dist/runtime/primitives.js.map +0 -1
  110. package/dist/runtime/runtime-api.d.ts +0 -2
  111. package/dist/runtime/runtime-api.js +0 -1
  112. package/dist/runtime/runtime-api.js.map +0 -1
  113. package/dist/runtime/workspace-contract.d.ts +0 -392
  114. package/dist/runtime/workspace-contract.js +0 -28
  115. package/dist/runtime/workspace-contract.js.map +0 -1
  116. package/dist/runtime-RJ5orDJU.d.ts +0 -1699
  117. package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
  118. package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
  119. package/dist/runtime.d.ts +0 -83
  120. package/dist/runtime.js +0 -242
  121. package/dist/runtime.js.map +0 -1
  122. package/dist/schema.d.ts +0 -379
  123. package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
  124. package/dist/testing-compiler.d.ts +0 -37
  125. package/dist/testing-compiler.js +0 -272
  126. package/dist/testing-compiler.js.map +0 -1
  127. package/dist/testing-runtime.d.ts +0 -119
  128. package/dist/testing-runtime.js +0 -212
  129. package/dist/testing-runtime.js.map +0 -1
  130. package/dist/types-DJj5MJkl.d.ts +0 -256
  131. package/dist/types-DR7DoB1x.d.ts +0 -28
  132. package/dist/types-JWCYHmu7.d.ts +0 -122
  133. package/dist/types.d.ts +0 -1958
  134. package/dist/types.js +0 -14
  135. package/dist/types.js.map +0 -1
  136. package/dist/ui/components.d.ts +0 -17
  137. package/dist/ui/components.js +0 -216
  138. package/dist/ui/components.js.map +0 -1
  139. package/dist/ui/defaults.d.ts +0 -19
  140. package/dist/ui/defaults.js +0 -104
  141. package/dist/ui/defaults.js.map +0 -1
  142. package/dist/ui/player-state.d.ts +0 -2
  143. package/dist/ui/player-state.js +0 -1
  144. package/dist/ui/player-state.js.map +0 -1
  145. package/dist/ui/plugin-styles.css +0 -2
  146. package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
  147. package/dist/ui.d.ts +0 -317
  148. package/dist/ui.js +0 -277
  149. package/dist/ui.js.map +0 -1
  150. package/dist/views-BpuWcOyN.d.ts +0 -1641
@@ -0,0 +1,1792 @@
1
+ import {
2
+ BoardProjectionSchema,
3
+ immutableCopy,
4
+ inputDomainErrors,
5
+ inputTargetInDomain,
6
+ inputValueInDomain,
7
+ inputValueKey,
8
+ requireLookup,
9
+ zonePositions
10
+ } from "./chunk-CZRPUWCT.js";
11
+
12
+ // src/headless/runtime-features.ts
13
+ var runtimeFeatures = /* @__PURE__ */ Symbol("runtimeFeatures");
14
+
15
+ // src/headless/input-control.ts
16
+ function resourceValue(domain, value) {
17
+ const record = value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
18
+ return Object.fromEntries(
19
+ domain.resources.map((resource) => {
20
+ const amount = record[resource.resourceId];
21
+ return [resource.resourceId, typeof amount === "number" ? amount : 0];
22
+ })
23
+ );
24
+ }
25
+ function createInputControl(input, props) {
26
+ const domain = input.getDomain();
27
+ const current = input.getValue();
28
+ const common = { key: input.key, disabled: props.disabled, props };
29
+ if (domain.type === "boundedNumber") {
30
+ if (domain.selection?.mode === "many")
31
+ return {
32
+ ...common,
33
+ type: "boundedNumber",
34
+ domain,
35
+ mode: "many",
36
+ value: Array.isArray(current) ? current.filter(
37
+ (value) => typeof value === "number"
38
+ ) : [],
39
+ setValue: (value) => input.setValue(value)
40
+ };
41
+ return {
42
+ ...common,
43
+ type: "boundedNumber",
44
+ domain,
45
+ mode: "single",
46
+ value: typeof current === "number" ? current : void 0,
47
+ setValue: (value) => value === void 0 ? input.clear() : input.setValue(value)
48
+ };
49
+ }
50
+ if (domain.type === "resourceMap") {
51
+ if (domain.selection?.mode === "many")
52
+ return {
53
+ ...common,
54
+ type: "resourceMap",
55
+ domain,
56
+ mode: "many",
57
+ value: Array.isArray(current) ? current.map((value) => resourceValue(domain, value)) : [],
58
+ setValue: (value) => input.setValue(value)
59
+ };
60
+ return {
61
+ ...common,
62
+ type: "resourceMap",
63
+ domain,
64
+ mode: "single",
65
+ value: current === void 0 ? void 0 : resourceValue(domain, current),
66
+ setValue: (value) => value === void 0 ? input.clear() : input.setValue(value)
67
+ };
68
+ }
69
+ return { ...common, type: "targets", options: input.getTargetOptions() };
70
+ }
71
+
72
+ // src/headless/instance.ts
73
+ import { createStore } from "@tanstack/store";
74
+
75
+ // src/headless/interaction-inputs.ts
76
+ function interactionInputKeys(descriptor) {
77
+ return descriptor.inputs.map((input) => input.key);
78
+ }
79
+ function applyInteractionInputDefaults(descriptor, params) {
80
+ const next = descriptor.step ? Object.fromEntries(
81
+ Object.entries(params).filter(
82
+ ([key]) => descriptor.inputs.some((input) => input.key === key)
83
+ )
84
+ ) : { ...params };
85
+ for (const input of descriptor.inputs) {
86
+ if (next[input.key] !== void 0) continue;
87
+ if (!("defaultValue" in input)) continue;
88
+ next[input.key] = input.defaultValue;
89
+ }
90
+ return next;
91
+ }
92
+ function validateInteractionInputDomains(descriptor, params) {
93
+ const fieldErrors = {};
94
+ for (const rawInput of descriptor.inputs) {
95
+ const input = rawInput;
96
+ const value = params[input.key];
97
+ if (value === void 0) continue;
98
+ for (const error of inputDomainErrors(
99
+ input.domain,
100
+ value,
101
+ input.domain.selection
102
+ ))
103
+ pushFieldError(fieldErrors, input.key, error);
104
+ }
105
+ return fieldErrors;
106
+ }
107
+ function isInputValueReady(input, value) {
108
+ if (value === void 0) return false;
109
+ if (value === null)
110
+ return input.domain.type === "choice" && (input.domain.choices ?? []).some(
111
+ (choice) => choice.value === null && !choice.disabled
112
+ );
113
+ const selection = inputSelection(input);
114
+ if (selection?.mode !== "many") return true;
115
+ return Array.isArray(value) && value.length >= selection.min;
116
+ }
117
+ function isManyInput(input) {
118
+ return inputSelection(input)?.mode === "many";
119
+ }
120
+ function toggleManyValue(current, value, selection) {
121
+ if (selection.mode !== "many") return [value];
122
+ const previous = Array.isArray(current) ? current : [];
123
+ const existing = previous.findIndex((item) => sameValue(item, value));
124
+ if (existing >= 0) {
125
+ return previous.filter((item) => !sameValue(item, value));
126
+ }
127
+ if (selection.max !== void 0 && previous.length >= selection.max) {
128
+ return previous;
129
+ }
130
+ return [...previous, value];
131
+ }
132
+ function isManyTargetSelectable(input, current, targetId) {
133
+ const selection = inputSelection(input);
134
+ if (selection?.mode !== "many") return true;
135
+ const currentValues = Array.isArray(current) ? current : [];
136
+ if (currentValues.some((item) => sameValue(item, targetId))) return true;
137
+ return selection.max === void 0 || currentValues.length < selection.max;
138
+ }
139
+ function hasInteractionFieldErrors(fieldErrors) {
140
+ return Object.values(fieldErrors).some(
141
+ (messages) => (messages?.length ?? 0) > 0
142
+ );
143
+ }
144
+ function inputByKey(descriptor, key) {
145
+ return descriptor.inputs.find((input) => input.key === key);
146
+ }
147
+ function inputSelection(input) {
148
+ if (input.domain.selection) return input.domain.selection;
149
+ if (input.domain.type === "choiceList")
150
+ return {
151
+ mode: "many",
152
+ min: input.domain.min ?? 0,
153
+ max: input.domain.max ?? input.domain.choices.length
154
+ };
155
+ return void 0;
156
+ }
157
+ function pushFieldError(fieldErrors, key, message) {
158
+ fieldErrors[key] = [...fieldErrors[key] ?? [], message];
159
+ }
160
+ function sameValue(left, right) {
161
+ return inputValueKey(left) === inputValueKey(right);
162
+ }
163
+
164
+ // src/headless/interaction-router.ts
165
+ function applyInteractionDraftMutation(store, descriptor, mutations) {
166
+ const originalDraft = {
167
+ ...store.getDraft(descriptor.interactionKey)
168
+ };
169
+ const nextDraft = {
170
+ ...originalDraft
171
+ };
172
+ const currentKeys = new Set(interactionInputKeys(descriptor));
173
+ for (const { key, value } of mutations) {
174
+ if (!descriptor.step || currentKeys.has(key)) nextDraft[key] = value;
175
+ }
176
+ if (descriptor.step) {
177
+ for (const key of Object.keys(nextDraft))
178
+ if (!currentKeys.has(key)) delete nextDraft[key];
179
+ }
180
+ const allKeys = /* @__PURE__ */ new Set([
181
+ ...Object.keys(originalDraft),
182
+ ...Object.keys(nextDraft)
183
+ ]);
184
+ for (const key of allKeys) {
185
+ if (!(key in nextDraft)) {
186
+ store.clearInput(descriptor.interactionKey, key);
187
+ continue;
188
+ }
189
+ if (originalDraft[key] !== nextDraft[key]) {
190
+ store.setInput(descriptor.interactionKey, key, nextDraft[key]);
191
+ }
192
+ }
193
+ return nextDraft;
194
+ }
195
+ function getInteractionDraftReadiness(descriptor, draft) {
196
+ const values = applyInteractionInputDefaults(
197
+ descriptor,
198
+ draft
199
+ );
200
+ const missingInputs = interactionInputKeys(descriptor).filter((key) => {
201
+ const input = inputByKey(descriptor, key);
202
+ const value = values[key];
203
+ return input ? !isInputValueReady(input, value) : value === null || value === void 0;
204
+ });
205
+ const fieldErrors = validateInteractionInputDomains(descriptor, values);
206
+ return {
207
+ values,
208
+ missingInputs,
209
+ fieldErrors,
210
+ ready: missingInputs.length === 0 && !hasInteractionFieldErrors(fieldErrors)
211
+ };
212
+ }
213
+ function routeInteractionTarget(store, descriptor, target) {
214
+ const input = inputByKey(descriptor, target.inputKey);
215
+ const currentDraft = store.getDraft(descriptor.interactionKey);
216
+ const selection = input ? inputSelection(input) : void 0;
217
+ const targetValue = selection?.mode === "many" ? toggleManyValue(
218
+ currentDraft[target.inputKey] ?? input?.defaultValue,
219
+ target.value,
220
+ selection
221
+ ) : target.value;
222
+ const params = applyInteractionDraftMutation(store, descriptor, [
223
+ ...Object.entries(target.extraInputs ?? {}).map(([key, value]) => ({
224
+ key,
225
+ value
226
+ })),
227
+ { key: target.inputKey, value: targetValue }
228
+ ]);
229
+ return {
230
+ params,
231
+ readiness: getInteractionDraftReadiness(descriptor, params)
232
+ };
233
+ }
234
+ function routeCardInputIntent(store, descriptor, intent) {
235
+ return routeInteractionTarget(store, descriptor, {
236
+ inputKey: intent.cardInputKey,
237
+ value: intent.cardId,
238
+ extraInputs: intent.dropTarget ? { [intent.dropTarget.inputKey]: intent.dropTarget.value } : void 0
239
+ });
240
+ }
241
+ function shouldAutoSubmitInteraction(descriptor) {
242
+ return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) => input.domain.selection?.mode === "many");
243
+ }
244
+
245
+ // src/headless/instance.ts
246
+ var EMPTY = Object.freeze({});
247
+ var EMPTY_DRAFTS = Object.freeze({});
248
+ var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" && inputValueKey(a) === inputValueKey(b);
249
+ function inDomain(input, value, partial = false) {
250
+ return inputValueInDomain(input.domain, value, input.domain.selection, {
251
+ ignoreMinimum: partial
252
+ });
253
+ }
254
+ function matchesBoardTarget(input, target) {
255
+ const domain = input.domain;
256
+ return domain.type === "boardTarget" && domain.valueKind === target.valueKind && domain.targetKind === target.kind && (domain.valueKind === "board-id" && target.valueKind === "board-id" ? domain.boardId === target.boardId : domain.valueKind === "board-space" && target.valueKind === "board-space" && domain.eligibleTargets.some(
257
+ (candidate) => candidate.boardId === target.value.boardId
258
+ ));
259
+ }
260
+ function immutableValues(value) {
261
+ return immutableCopy(value);
262
+ }
263
+ function data(action, interaction, input, value) {
264
+ return {
265
+ "data-action": action,
266
+ "data-interaction": interaction,
267
+ ...input ? { "data-input": input } : {},
268
+ ...value === void 0 ? {} : {
269
+ "data-value": typeof value === "string" ? value : JSON.stringify(value)
270
+ }
271
+ };
272
+ }
273
+ var AmbiguousTargetError = class extends Error {
274
+ constructor(id) {
275
+ super(
276
+ `Target '${id}' belongs to more than one eligible input. Choose an interaction and input explicitly.`
277
+ );
278
+ this.name = "AmbiguousTargetError";
279
+ }
280
+ };
281
+ var InteractionObject = class {
282
+ constructor(owner, descriptor, draft, status, connected) {
283
+ this.owner = owner;
284
+ this.descriptor = descriptor;
285
+ this.status = status;
286
+ this.connected = connected;
287
+ this.seat = descriptor.actorSeat ?? owner.sourceState.snapshot?.players.findIndex(
288
+ (player) => player.playerId === owner.sourceState.snapshot?.me
289
+ ) ?? -1;
290
+ this.draftIdentity = draft;
291
+ this.draft = immutableValues(draft);
292
+ this.source = owner.options.source;
293
+ this.epoch = owner.epoch;
294
+ this.key = descriptor.interactionKey;
295
+ this.id = descriptor.interactionId;
296
+ this.phase = descriptor.phaseName;
297
+ this.label = descriptor.label;
298
+ this.help = descriptor.help;
299
+ this.kind = descriptor.step ? "steps" : "inputs";
300
+ this.inputObjects = Object.freeze(
301
+ descriptor.inputs.map(
302
+ (input) => owner.object("input", new InputObject(this, input))
303
+ )
304
+ );
305
+ }
306
+ owner;
307
+ descriptor;
308
+ status;
309
+ connected;
310
+ key;
311
+ id;
312
+ phase;
313
+ label;
314
+ help;
315
+ kind;
316
+ inputObjects;
317
+ seat;
318
+ draft;
319
+ draftIdentity;
320
+ source;
321
+ epoch;
322
+ get game() {
323
+ return this.owner.instance;
324
+ }
325
+ getAvailability() {
326
+ return this.descriptor.availability;
327
+ }
328
+ getIsAvailable(params) {
329
+ const descriptor = params === void 0 ? this.descriptor : this.owner.descriptorFor(this.descriptor, params);
330
+ return descriptor.availability.status === "available";
331
+ }
332
+ getUnavailableReason() {
333
+ return this.descriptor.availability.status === "available" ? null : this.descriptor.availability.reason;
334
+ }
335
+ getStep() {
336
+ return this.descriptor.step ?? null;
337
+ }
338
+ getStepIndex() {
339
+ return this.descriptor.step?.index ?? null;
340
+ }
341
+ getInput(key) {
342
+ return requireLookup(
343
+ this.findInput(key),
344
+ `Input in interaction ${this.key}`,
345
+ key
346
+ );
347
+ }
348
+ findInput(key) {
349
+ return this.inputObjects.find((input) => input.key === key);
350
+ }
351
+ getInputs() {
352
+ return this.inputObjects;
353
+ }
354
+ readiness(params = this.draft) {
355
+ return getInteractionDraftReadiness(
356
+ this.owner.descriptorFor(this.descriptor, params),
357
+ params
358
+ );
359
+ }
360
+ getIsReady(params) {
361
+ return this.readiness(params).ready;
362
+ }
363
+ getMissingInputs() {
364
+ return this.readiness().missingInputs;
365
+ }
366
+ getStatus() {
367
+ return this.status;
368
+ }
369
+ currentLifetime() {
370
+ return this.source === this.owner.options.source && this.epoch === this.owner.epoch && !this.owner.disposed;
371
+ }
372
+ submit(params) {
373
+ return this.currentLifetime() ? this.owner.submit(this.key, false, params) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
374
+ }
375
+ cancel() {
376
+ return this.currentLifetime() ? this.owner.submit(this.key, true) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
377
+ }
378
+ reset() {
379
+ if (this.currentLifetime()) this.owner.reset(this.key);
380
+ }
381
+ activate() {
382
+ if (this.currentLifetime()) this.owner.activate(this.key);
383
+ }
384
+ getSubmitHandler(params) {
385
+ return (event) => {
386
+ event?.preventDefault();
387
+ this.owner.handle(() => this.submit(params));
388
+ };
389
+ }
390
+ getSubmitProps(params) {
391
+ const disabled = !this.getIsAvailable(params) || !this.getIsReady(params) || this.status !== "open" || !this.connected;
392
+ return {
393
+ ...data("submit", this.key),
394
+ "data-seat": this.seat,
395
+ type: "button",
396
+ disabled,
397
+ "data-ready": this.getIsReady(params),
398
+ "data-disabled": disabled,
399
+ onClick: this.getSubmitHandler(params)
400
+ };
401
+ }
402
+ };
403
+ function createInputTargetLabels(domain, snapshot, boards) {
404
+ const targetLabels = /* @__PURE__ */ new Map();
405
+ const hosts = Object.values(snapshot?.frame.zones ?? {}).flatMap(
406
+ (zones) => Object.values(zones)
407
+ );
408
+ if (domain.type === "cardTarget") {
409
+ domain.eligibleTargets.forEach((id, index) => {
410
+ const card = hosts.map((zone) => zone.cardViewsById[id]).find((card2) => card2 !== void 0);
411
+ targetLabels.set(
412
+ inputValueKey(id),
413
+ card ? card.name || `Card ${index + 1}` : `Card back ${index + 1}`
414
+ );
415
+ });
416
+ }
417
+ if (domain.type === "zonePosition")
418
+ for (const value of zonePositions(domain.zones)) {
419
+ const zone = snapshot?.frame.zones[value.zoneId]?.[value.hostId];
420
+ const id = zone?.cardIds[value.index];
421
+ const name = id === void 0 ? void 0 : zone?.cardViewsById[id]?.name;
422
+ const place = id === void 0 ? "At the end" : `Before ${name || `card ${value.index + 1}`}`;
423
+ targetLabels.set(
424
+ inputValueKey(value),
425
+ domain.zones.length === 1 ? place : `${value.zoneId} (${value.hostId}): ${place}`
426
+ );
427
+ }
428
+ if (domain.type === "tileTarget" || domain.type === "boardTarget") {
429
+ if (domain.type === "tileTarget") {
430
+ const presentations = [
431
+ ...hosts.flatMap((zone) => zone.tiles),
432
+ ...Object.values(boards).flatMap(
433
+ (board) => board.layout === "generic" ? [] : board.tiles
434
+ )
435
+ ];
436
+ domain.eligibleTargets.forEach((ref, index) => {
437
+ const tile = presentations.find((tile2) => tile2.ref === ref);
438
+ targetLabels.set(
439
+ inputValueKey(ref),
440
+ tile?.disclosure === "visible" ? tile.name || `Tile ${index + 1}` : `Tile back ${index + 1}`
441
+ );
442
+ });
443
+ } else {
444
+ domain.eligibleTargets.forEach((value, index) => {
445
+ const boardId = typeof value === "string" ? domain.valueKind === "board-id" ? domain.boardId : void 0 : value.boardId;
446
+ const id = typeof value === "string" ? value : value.spaceId;
447
+ const board = boardId === void 0 ? void 0 : boards[boardId];
448
+ const name = domain.targetKind === "space" ? board?.spaces[id]?.name : board && board.layout !== "generic" ? (domain.targetKind === "edge" ? board.edges : board.vertices).find((target) => target.id === id)?.label : void 0;
449
+ const kind = domain.targetKind === "space" ? "Space" : domain.targetKind === "edge" ? "Edge" : "Vertex";
450
+ targetLabels.set(inputValueKey(value), name || `${kind} ${index + 1}`);
451
+ });
452
+ }
453
+ }
454
+ return targetLabels;
455
+ }
456
+ var InputObject = class {
457
+ constructor(interaction, descriptor) {
458
+ this.interaction = interaction;
459
+ this.descriptor = descriptor;
460
+ this.key = descriptor.key;
461
+ this.kind = descriptor.kind;
462
+ const snapshot = interaction.owner.sourceState.snapshot;
463
+ this.snapshot = snapshot;
464
+ this.targetLabels = createInputTargetLabels(
465
+ descriptor.domain,
466
+ snapshot,
467
+ descriptor.domain.type === "tileTarget" || descriptor.domain.type === "boardTarget" ? interaction.owner.getBoardProjections(snapshot?.frame.view) : {}
468
+ );
469
+ }
470
+ interaction;
471
+ descriptor;
472
+ targetLabels;
473
+ snapshot;
474
+ key;
475
+ kind;
476
+ currentLifetime() {
477
+ return this.interaction.currentLifetime() && (this.descriptor.domain.type !== "tileTarget" || this.snapshot === this.interaction.owner.sourceState.snapshot);
478
+ }
479
+ get game() {
480
+ return this.interaction.game;
481
+ }
482
+ get domainType() {
483
+ return this.descriptor.domain.type;
484
+ }
485
+ get selectionMode() {
486
+ return isManyInput(this.descriptor) ? "many" : "single";
487
+ }
488
+ getTargetOptions() {
489
+ const domain = this.getDomain();
490
+ return this.getEligibleTargets().map((value) => ({
491
+ value,
492
+ label: this.targetLabels.get(inputValueKey(value)) ?? (domain.type === "choice" || domain.type === "choiceList" ? domain.choices.find((choice) => Object.is(choice.value, value))?.label : void 0) ?? (value === null ? "None" : typeof value === "object" ? JSON.stringify(value) : String(value)),
493
+ selected: this.getIsSelected(value),
494
+ props: this.getTargetProps(value)
495
+ }));
496
+ }
497
+ getDomain() {
498
+ return this.descriptor.domain;
499
+ }
500
+ getValue() {
501
+ return this.interaction.draft[this.key] ?? (Object.hasOwn(this.interaction.draft, this.key) ? null : this.descriptor.defaultValue);
502
+ }
503
+ setValue(value) {
504
+ if (!this.currentLifetime()) return;
505
+ this.interaction.owner.setInput(this.interaction.key, this.key, value);
506
+ }
507
+ clear() {
508
+ if (!this.currentLifetime()) return;
509
+ this.interaction.owner.clearInput(this.interaction.key, this.key);
510
+ }
511
+ getIsReady() {
512
+ const descriptor = {
513
+ ...this.interaction.descriptor,
514
+ inputs: [this.descriptor]
515
+ };
516
+ return getInteractionDraftReadiness(descriptor, {
517
+ [this.key]: this.getValue()
518
+ }).ready;
519
+ }
520
+ getEligibleTargets() {
521
+ const domain = this.descriptor.domain;
522
+ if (domain.type === "cardTarget" || domain.type === "boardTarget" || domain.type === "tileTarget")
523
+ return domain.eligibleTargets;
524
+ if (domain.type === "choice" || domain.type === "choiceList")
525
+ return domain.choices.filter((choice) => !choice.disabled).map((choice) => choice.value);
526
+ if (domain.type === "zonePosition") return zonePositions(domain.zones);
527
+ return [];
528
+ }
529
+ getIsEligible(value) {
530
+ const input = this.descriptor;
531
+ return inputTargetInDomain(input.domain, value) && isManyTargetSelectable(input, this.getValue(), value);
532
+ }
533
+ getIsSelected(value) {
534
+ const current = this.getValue();
535
+ return isManyInput(this.descriptor) && Array.isArray(current) ? current.some((item) => equalValue(item, value)) : equalValue(current, value);
536
+ }
537
+ getSelectHandler(value) {
538
+ return () => {
539
+ if (this.currentLifetime())
540
+ this.interaction.owner.select(this.interaction.key, this.key, value);
541
+ };
542
+ }
543
+ getTargetProps(value) {
544
+ const eligible = this.getIsEligible(value);
545
+ const disabled = !eligible || !this.interaction.getIsAvailable() || this.interaction.status !== "open" || !this.interaction.connected;
546
+ return {
547
+ ...data("select", this.interaction.key, this.key, value),
548
+ "data-seat": this.interaction.seat,
549
+ type: "button",
550
+ disabled,
551
+ "data-eligible": eligible,
552
+ "data-selected": this.getIsSelected(value),
553
+ "data-disabled": disabled,
554
+ onClick: () => this.getSelectHandler(value)()
555
+ };
556
+ }
557
+ getControl() {
558
+ const disabled = !this.interaction.getIsAvailable() || this.interaction.status !== "open" || !this.interaction.connected;
559
+ return createInputControl(this, {
560
+ ...data("option", this.interaction.key, this.key),
561
+ "data-seat": this.interaction.seat,
562
+ disabled,
563
+ "data-disabled": disabled
564
+ });
565
+ }
566
+ };
567
+ var CardObject = class {
568
+ constructor(owner, id, zone, hostId, index, view, backImage, routes) {
569
+ this.owner = owner;
570
+ this.id = id;
571
+ this.zone = zone;
572
+ this.hostId = hostId;
573
+ this.index = index;
574
+ this.routes = routes;
575
+ this.epoch = owner.epoch;
576
+ this.seat = owner.sourceState.snapshot?.players.findIndex(
577
+ (player) => player.playerId === owner.sourceState.snapshot?.me
578
+ ) ?? -1;
579
+ this.view = view ?? null;
580
+ this.hidden = this.view === null;
581
+ this.backImage = backImage ?? null;
582
+ }
583
+ owner;
584
+ id;
585
+ zone;
586
+ hostId;
587
+ index;
588
+ routes;
589
+ epoch;
590
+ seat;
591
+ view;
592
+ hidden;
593
+ backImage;
594
+ get game() {
595
+ return this.owner.instance;
596
+ }
597
+ getInteractions() {
598
+ return this.routes;
599
+ }
600
+ getIsEligible() {
601
+ return this.routes.some((route) => route.getIsAvailable());
602
+ }
603
+ getIsSelected() {
604
+ return this.routes.some(
605
+ (route) => route.getInputs().some(
606
+ (input) => input.descriptor.domain.type === "cardTarget" && input.getIsSelected(this.id)
607
+ )
608
+ );
609
+ }
610
+ getCanSelect(options) {
611
+ return this.routes.some(
612
+ (route) => route.getIsAvailable() && (!options?.interaction || route.key === options.interaction) && route.status === "open" && route.connected && route.getInputs().some(
613
+ (input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(this.id)
614
+ )
615
+ );
616
+ }
617
+ select(options) {
618
+ if (this.epoch !== this.owner.epoch || this.owner.disposed) return;
619
+ this.owner.selectCard(this.id, options);
620
+ }
621
+ getSelectHandler(options) {
622
+ return () => this.select(options);
623
+ }
624
+ getProps(options) {
625
+ return {
626
+ type: "button",
627
+ disabled: !this.getCanSelect(options),
628
+ "data-action": "select",
629
+ "data-value": this.id,
630
+ "data-interaction": options?.interaction,
631
+ "data-input": options?.input,
632
+ "data-eligible": this.getIsEligible(),
633
+ "data-selected": this.getIsSelected(),
634
+ "data-disabled": !this.getCanSelect(options),
635
+ "data-hidden": this.hidden,
636
+ "data-seat": this.seat,
637
+ onClick: this.getSelectHandler(options)
638
+ };
639
+ }
640
+ };
641
+ var TileObject = class {
642
+ constructor(owner, data2, zone, hostId, index, routes) {
643
+ this.owner = owner;
644
+ this.data = data2;
645
+ this.zone = zone;
646
+ this.hostId = hostId;
647
+ this.index = index;
648
+ this.routes = routes;
649
+ this.epoch = owner.epoch;
650
+ this.snapshot = owner.sourceState.snapshot;
651
+ }
652
+ owner;
653
+ data;
654
+ zone;
655
+ hostId;
656
+ index;
657
+ routes;
658
+ epoch;
659
+ snapshot;
660
+ get ref() {
661
+ return this.data.ref;
662
+ }
663
+ get game() {
664
+ return this.owner.instance;
665
+ }
666
+ getInteractions() {
667
+ return this.routes;
668
+ }
669
+ getIsEligible() {
670
+ return this.routes.some(
671
+ (route) => route.getInputs().some(
672
+ (input) => input.descriptor.domain.type === "tileTarget" && input.getIsEligible(this.ref)
673
+ )
674
+ );
675
+ }
676
+ getIsSelected() {
677
+ return this.routes.some(
678
+ (route) => route.getInputs().some(
679
+ (input) => input.descriptor.domain.type === "tileTarget" && input.getIsSelected(this.ref)
680
+ )
681
+ );
682
+ }
683
+ getCanSelect(options) {
684
+ return this.routes.some(
685
+ (route) => route.getIsAvailable() && (!options?.interaction || route.key === options.interaction) && route.getInputs().some(
686
+ (input) => input.descriptor.domain.type === "tileTarget" && (!options?.input || input.key === options.input) && !input.getTargetProps(this.ref).disabled
687
+ )
688
+ );
689
+ }
690
+ select(options) {
691
+ if (this.owner.disposed || this.epoch !== this.owner.epoch || this.snapshot !== this.owner.sourceState.snapshot)
692
+ return;
693
+ this.owner.routeTarget({ kind: "tile", value: this.ref }, options);
694
+ }
695
+ getSelectHandler(options) {
696
+ return () => this.select(options);
697
+ }
698
+ getTargetProps(options) {
699
+ const disabled = !this.getCanSelect(options);
700
+ return {
701
+ type: "button",
702
+ disabled,
703
+ "data-action": "select",
704
+ "data-value": this.ref,
705
+ "data-interaction": options?.interaction,
706
+ "data-input": options?.input,
707
+ "data-eligible": this.getIsEligible(),
708
+ "data-selected": this.getIsSelected(),
709
+ "data-disabled": disabled,
710
+ "data-hidden": this.data.disclosure === "concealed",
711
+ onClick: this.getSelectHandler(options)
712
+ };
713
+ }
714
+ };
715
+ var ZoneObject = class {
716
+ constructor(owner, id, hostId, cards, tiles) {
717
+ this.owner = owner;
718
+ this.id = id;
719
+ this.hostId = hostId;
720
+ this.cards = cards;
721
+ this.tiles = tiles;
722
+ }
723
+ owner;
724
+ id;
725
+ hostId;
726
+ cards;
727
+ tiles;
728
+ get game() {
729
+ return this.owner.instance;
730
+ }
731
+ get count() {
732
+ return this.cards.length + this.tiles.length;
733
+ }
734
+ getIsEmpty() {
735
+ return this.count === 0;
736
+ }
737
+ getTiles() {
738
+ return this.tiles;
739
+ }
740
+ getTile(ref) {
741
+ return requireLookup(this.findTile(ref), `Tile in zone ${this.id}`, ref);
742
+ }
743
+ findTile(ref) {
744
+ return this.tiles.find((tile) => tile.ref === ref);
745
+ }
746
+ getCard(id) {
747
+ return requireLookup(this.findCard(id), `Card in zone ${this.id}`, id);
748
+ }
749
+ findCard(id) {
750
+ return this.cards.find((card) => card.id === id);
751
+ }
752
+ getCards(options) {
753
+ return options?.sort ? [...this.cards].sort(options.sort) : this.cards;
754
+ }
755
+ };
756
+ var PlayerObject = class {
757
+ constructor(owner, summary, index, isMe, active) {
758
+ this.owner = owner;
759
+ this.index = index;
760
+ this.isMe = isMe;
761
+ this.active = active;
762
+ this.id = summary.playerId;
763
+ this.name = summary.displayName;
764
+ this.color = summary.color;
765
+ }
766
+ owner;
767
+ index;
768
+ isMe;
769
+ active;
770
+ id;
771
+ name;
772
+ color;
773
+ get game() {
774
+ return this.owner.instance;
775
+ }
776
+ };
777
+ var PhaseObject = class {
778
+ constructor(current) {
779
+ this.current = current;
780
+ }
781
+ current;
782
+ is(name) {
783
+ return this.current === name;
784
+ }
785
+ switch(routes) {
786
+ return this.current === null ? null : routes[this.current]();
787
+ }
788
+ };
789
+ var Controller = class {
790
+ boardProjections = /* @__PURE__ */ new WeakMap();
791
+ getBoardProjections(view) {
792
+ if (view === null || typeof view !== "object" || Array.isArray(view))
793
+ return {};
794
+ const cached = this.boardProjections.get(view);
795
+ if (cached) return cached;
796
+ const payload = Reflect.get(view, "boards");
797
+ const boards = payload === void 0 ? {} : immutableCopy(BoardProjectionSchema.parse(payload));
798
+ this.boardProjections.set(view, boards);
799
+ return boards;
800
+ }
801
+ epoch = 0;
802
+ options;
803
+ localDrafts;
804
+ localActive;
805
+ disposed = false;
806
+ instance;
807
+ store;
808
+ prototypes = {
809
+ interaction: Object.create(InteractionObject.prototype),
810
+ input: Object.create(InputObject.prototype),
811
+ card: Object.create(CardObject.prototype),
812
+ zone: Object.create(ZoneObject.prototype),
813
+ board: {}
814
+ };
815
+ subscription;
816
+ sourceState;
817
+ reportedFailures = /* @__PURE__ */ new WeakMap();
818
+ pending = null;
819
+ building;
820
+ rootProperties = /* @__PURE__ */ new Map();
821
+ featureDisposals = [];
822
+ warned = /* @__PURE__ */ new Set();
823
+ invalidControlledState;
824
+ observed = /* @__PURE__ */ new Set();
825
+ revisions = /* @__PURE__ */ new Map();
826
+ lastDrafts = EMPTY_DRAFTS;
827
+ lastInteractions = [];
828
+ lastSnapshotDrafts;
829
+ trackDrafts() {
830
+ const drafts = this.drafts();
831
+ for (const key of /* @__PURE__ */ new Set([
832
+ ...Object.keys(this.lastDrafts),
833
+ ...Object.keys(drafts)
834
+ ]))
835
+ if (this.lastDrafts[key] !== drafts[key])
836
+ this.revisions.set(key, (this.revisions.get(key) ?? 0) + 1);
837
+ this.lastDrafts = drafts;
838
+ }
839
+ constructor(options, features) {
840
+ this.options = options;
841
+ this.localDrafts = options.initialState?.drafts ?? EMPTY_DRAFTS;
842
+ this.localActive = options.initialState?.activeInteraction ?? null;
843
+ this.sourceState = options.source.store.get();
844
+ const current = () => this.building ?? this.store.get();
845
+ const root = {
846
+ get snapshot() {
847
+ return current().snapshot;
848
+ },
849
+ get view() {
850
+ return current().view;
851
+ },
852
+ get version() {
853
+ return current().version;
854
+ },
855
+ get connection() {
856
+ return current().connection;
857
+ },
858
+ get failure() {
859
+ return current().failure;
860
+ },
861
+ get request() {
862
+ return current().request;
863
+ },
864
+ get state() {
865
+ return current().state;
866
+ },
867
+ get phase() {
868
+ return current().phase;
869
+ },
870
+ get turn() {
871
+ return current().turn;
872
+ },
873
+ get me() {
874
+ return current().me;
875
+ },
876
+ get players() {
877
+ return current().players;
878
+ },
879
+ get interactions() {
880
+ return current().interactions;
881
+ },
882
+ get inputs() {
883
+ return current().inputs;
884
+ },
885
+ get zones() {
886
+ return current().zones;
887
+ },
888
+ get cards() {
889
+ return current().cards;
890
+ },
891
+ get events() {
892
+ return current().events;
893
+ },
894
+ store: {
895
+ get: () => this.store.get(),
896
+ subscribe: (listener) => this.store.subscribe(listener)
897
+ },
898
+ getSnapshot: () => this.building ?? this.store.get(),
899
+ getOptions: () => this.options,
900
+ setOptions: (next) => this.setOptions(next),
901
+ subscribe: (listener) => {
902
+ const sub = this.store.subscribe(listener);
903
+ return () => sub.unsubscribe();
904
+ },
905
+ dispose: () => this.dispose(),
906
+ assertCoverage: () => this.assertCoverage()
907
+ };
908
+ for (const [key, descriptor] of Object.entries(
909
+ Object.getOwnPropertyDescriptors(root)
910
+ )) {
911
+ if (descriptor.get) {
912
+ Object.defineProperty(root, key, {
913
+ enumerable: false,
914
+ configurable: false
915
+ });
916
+ }
917
+ }
918
+ Object.defineProperty(root, "store", {
919
+ enumerable: false,
920
+ configurable: false,
921
+ writable: false
922
+ });
923
+ this.instance = root;
924
+ this.store = createStore(Object.freeze(this.build()));
925
+ if ("apply" in options.source)
926
+ Object.defineProperty(root, "apply", {
927
+ value: (action) => this.options.source.apply(action)
928
+ });
929
+ if ("explore" in options.source)
930
+ Object.defineProperty(root, "explore", {
931
+ value: (...args) => this.options.source.explore(...args)
932
+ });
933
+ const runtime = {
934
+ game: this.instance,
935
+ getBoards: () => this.getBoardProjections(this.instance.view),
936
+ createBoard: (data2) => this.object("board", {
937
+ id: data2.id,
938
+ data: immutableCopy(data2),
939
+ game: this.instance
940
+ }),
941
+ routeTarget: (target, options2) => this.routeTarget(target, options2),
942
+ getCanDropCard: (cardId, target) => this.prepareCardDrop(cardId, target) !== void 0,
943
+ routeCardDrop: (cardId, target) => this.routeCardDrop(cardId, target),
944
+ getCanDropCardGroup: (cardIds, target) => !!this.prepareCardDrop(cardIds, target),
945
+ routeCardGroupDrop: (cardIds, target) => this.routeCardDrop(cardIds, target),
946
+ invalidate: () => {
947
+ if (!this.disposed) this.refresh();
948
+ }
949
+ };
950
+ const context = { ...runtime, [runtimeFeatures]: runtime };
951
+ for (const feature of Object.values(
952
+ features?.(this.instance, context) ?? {}
953
+ )) {
954
+ if (feature.root)
955
+ for (const key of Reflect.ownKeys(feature.root)) {
956
+ if (key in root || this.rootProperties.has(key))
957
+ throw new Error(`Overlapping feature property '${String(key)}'.`);
958
+ this.rootProperties.set(
959
+ key,
960
+ Object.getOwnPropertyDescriptor(feature.root, key)
961
+ );
962
+ Object.defineProperty(root, key, {
963
+ get: () => Reflect.get(this.building ?? this.store.get(), key)
964
+ });
965
+ }
966
+ for (const hook of [
967
+ "interaction",
968
+ "input",
969
+ "zone",
970
+ "card",
971
+ "board"
972
+ ])
973
+ this.install(this.prototypes[hook], feature[hook]);
974
+ if (feature.dispose) this.featureDisposals.push(feature.dispose);
975
+ }
976
+ this.refresh();
977
+ this.subscribeSource();
978
+ this.reportSourceFailure();
979
+ }
980
+ install(target, extension) {
981
+ if (!extension) return;
982
+ for (const key of Reflect.ownKeys(extension)) {
983
+ if (key in target || [
984
+ "game",
985
+ "owner",
986
+ "descriptor",
987
+ "draft",
988
+ "inputObjects",
989
+ "status",
990
+ "connected",
991
+ "key",
992
+ "id",
993
+ "phase",
994
+ "label",
995
+ "help",
996
+ "kind",
997
+ "interaction",
998
+ "view",
999
+ "hidden",
1000
+ "zone",
1001
+ "index",
1002
+ "routes",
1003
+ "cards"
1004
+ ].includes(String(key)))
1005
+ throw new Error(`Overlapping feature property '${String(key)}'.`);
1006
+ }
1007
+ Object.defineProperties(
1008
+ target,
1009
+ Object.getOwnPropertyDescriptors(extension)
1010
+ );
1011
+ }
1012
+ object(hook, value) {
1013
+ Object.setPrototypeOf(value, this.prototypes[hook]);
1014
+ return Object.freeze(value);
1015
+ }
1016
+ drafts() {
1017
+ if (this.options.state && this.options.state === this.invalidControlledState)
1018
+ return EMPTY_DRAFTS;
1019
+ return this.options.state?.drafts ?? this.localDrafts;
1020
+ }
1021
+ active() {
1022
+ if (this.options.state && this.options.state === this.invalidControlledState)
1023
+ return null;
1024
+ return this.options.state?.activeInteraction !== void 0 ? this.options.state.activeInteraction : this.localActive;
1025
+ }
1026
+ writeDrafts(next) {
1027
+ if (this.options.state?.drafts === void 0) this.localDrafts = next;
1028
+ this.options.onDraftsChange?.(next);
1029
+ this.refresh();
1030
+ }
1031
+ writeDraft(key, value) {
1032
+ const drafts = this.drafts();
1033
+ const next = { ...drafts };
1034
+ if (value === void 0) delete next[key];
1035
+ else next[key] = immutableValues(value);
1036
+ this.writeDrafts(Object.freeze(next));
1037
+ }
1038
+ activate(key) {
1039
+ if (this.disposed || key !== null && !this.current(key)) return;
1040
+ if (this.options.state?.activeInteraction === void 0)
1041
+ this.localActive = key;
1042
+ this.options.onActiveInteractionChange?.(key);
1043
+ this.refresh();
1044
+ }
1045
+ descriptorFor(base, draft) {
1046
+ const zones = this.sourceState.snapshot?.frame.zones ?? {};
1047
+ for (const input of base.inputs) {
1048
+ const card = draft[input.key];
1049
+ if (input.domain.type !== "cardTarget" || typeof card !== "string")
1050
+ continue;
1051
+ for (const zone of Object.values(zones).flatMap(
1052
+ (hosts) => Object.values(hosts)
1053
+ )) {
1054
+ const route = zone.playableByCardId[card]?.find(
1055
+ (value) => value.interactionKey === base.interactionKey
1056
+ );
1057
+ if (route) return route;
1058
+ }
1059
+ }
1060
+ return base;
1061
+ }
1062
+ current(key) {
1063
+ return this.store.get().interactions.find(key);
1064
+ }
1065
+ editable(key) {
1066
+ const interaction = this.current(key);
1067
+ return !this.disposed && this.sourceState.connection === "ready" && !this.sourceState.request && interaction?.getIsAvailable() ? interaction : void 0;
1068
+ }
1069
+ setInput(key, inputKey, value) {
1070
+ const interaction = this.editable(key);
1071
+ if (!interaction?.findInput(inputKey)) return;
1072
+ this.writeDraft(key, { ...this.drafts()[key], [inputKey]: value });
1073
+ }
1074
+ clearInput(key, inputKey) {
1075
+ if (!this.editable(key)?.findInput(inputKey)) return;
1076
+ const value = { ...this.drafts()[key] };
1077
+ delete value[inputKey];
1078
+ this.writeDraft(key, value);
1079
+ }
1080
+ reset(key) {
1081
+ if (this.disposed) return;
1082
+ this.writeDraft(key, void 0);
1083
+ if (this.active() === key) this.activate(null);
1084
+ }
1085
+ select(key, inputKey, value, route) {
1086
+ const interaction = route ?? this.editable(key);
1087
+ if (route && (!route.currentLifetime() || !route.getIsAvailable() || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending))
1088
+ return;
1089
+ const input = interaction?.findInput(inputKey);
1090
+ if (!interaction || !input?.getIsEligible(value)) return;
1091
+ if (isManyInput(input.descriptor) || typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget" || input.descriptor.domain.type === "tileTarget")) {
1092
+ let next = { ...this.drafts()[key] };
1093
+ routeInteractionTarget(
1094
+ {
1095
+ getDraft: () => next,
1096
+ setInput: (_key, k, v) => {
1097
+ next = { ...next, [k]: v };
1098
+ },
1099
+ clearInput: (_key, k) => {
1100
+ if (k) delete next[k];
1101
+ }
1102
+ },
1103
+ interaction.descriptor,
1104
+ { inputKey, value }
1105
+ );
1106
+ this.writeDraft(key, next);
1107
+ } else this.setInput(key, inputKey, value);
1108
+ this.activate(key);
1109
+ const current = this.current(key);
1110
+ if (current && Object.hasOwn(this.drafts()[key] ?? EMPTY, inputKey) && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
1111
+ this.handle(() => this.submit(key, false));
1112
+ }
1113
+ selectCard(id, options) {
1114
+ const card = this.store.get().cards.find(id);
1115
+ if (!card || this.sourceState.request || this.sourceState.connection !== "ready")
1116
+ return;
1117
+ const candidates = card.routes.flatMap((interaction) => {
1118
+ if (!interaction.getIsAvailable() || !interaction.currentLifetime() || options?.interaction && interaction.key !== options.interaction)
1119
+ return [];
1120
+ return interaction.getInputs().filter(
1121
+ (input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(id)
1122
+ ).map((input) => ({ interaction, input }));
1123
+ });
1124
+ if (candidates.length > 1) throw new AmbiguousTargetError(id);
1125
+ const candidate = candidates[0];
1126
+ if (candidate)
1127
+ this.select(
1128
+ candidate.interaction.key,
1129
+ candidate.input.key,
1130
+ id,
1131
+ candidate.interaction
1132
+ );
1133
+ }
1134
+ handle(submit) {
1135
+ const source = this.options.source;
1136
+ const active = () => !this.disposed && this.options.source === source;
1137
+ void submit().then(
1138
+ (result) => {
1139
+ if (active() && !result.accepted)
1140
+ this.options.onError?.(
1141
+ new Error(result.message ?? result.errorCode, { cause: result })
1142
+ );
1143
+ },
1144
+ (error) => {
1145
+ if (!active()) return;
1146
+ const state = source.store.get();
1147
+ if (state.failure === error || state.connection === "closed") return;
1148
+ this.options.onError?.(error);
1149
+ }
1150
+ );
1151
+ }
1152
+ async submit(key, cancel, params) {
1153
+ const source = this.options.source;
1154
+ const interaction = this.current(key);
1155
+ if (this.disposed || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending)
1156
+ return { accepted: false, errorCode: "BUSY" };
1157
+ if (!interaction) return { accepted: false, errorCode: "UNAVAILABLE" };
1158
+ if (cancel ? !interaction.getStep()?.canCancel : !interaction.getIsAvailable(params) || !interaction.getIsReady(params))
1159
+ return { accepted: false, errorCode: "UNAVAILABLE" };
1160
+ if (!("submit" in source) || !("cancel" in source))
1161
+ return { accepted: false, errorCode: "READ_ONLY" };
1162
+ const commandSource = source;
1163
+ const operation = {
1164
+ source,
1165
+ key,
1166
+ draftRevision: params === void 0 ? this.revisions.get(key) ?? 0 : void 0,
1167
+ version: this.sourceState.snapshot.version,
1168
+ epoch: this.epoch,
1169
+ accepted: false,
1170
+ cancel
1171
+ };
1172
+ this.pending = operation;
1173
+ try {
1174
+ const result = await (cancel ? commandSource.cancel(interaction.id) : commandSource.submit(
1175
+ interaction.id,
1176
+ interaction.readiness(params).values
1177
+ ));
1178
+ if (this.pending !== operation) return result;
1179
+ if (!result.accepted) {
1180
+ this.pending = null;
1181
+ this.refresh();
1182
+ return result;
1183
+ }
1184
+ operation.accepted = true;
1185
+ this.finishBarrier();
1186
+ return result;
1187
+ } catch (error) {
1188
+ if (this.pending === operation) {
1189
+ this.pending = null;
1190
+ this.refresh();
1191
+ }
1192
+ throw error;
1193
+ }
1194
+ }
1195
+ finishBarrier() {
1196
+ const operation = this.pending;
1197
+ if (!operation?.accepted || this.sourceState.request || operation.source !== this.options.source || operation.epoch !== this.epoch)
1198
+ return;
1199
+ if (this.sourceState.connection === "closed" || this.sourceState.connection === "failed") {
1200
+ this.pending = null;
1201
+ this.refresh();
1202
+ return;
1203
+ }
1204
+ if (this.sourceState.connection !== "ready" || (this.sourceState.snapshot?.version ?? -1) <= operation.version)
1205
+ return;
1206
+ this.pending = null;
1207
+ if (operation.draftRevision !== void 0 && (this.revisions.get(operation.key) ?? 0) === operation.draftRevision)
1208
+ this.reset(operation.key);
1209
+ else this.refresh();
1210
+ }
1211
+ reportSourceFailure() {
1212
+ const failure = this.sourceState.failure;
1213
+ if (!failure || !this.options.onError || this.reportedFailures.get(this.options.source) === failure)
1214
+ return;
1215
+ this.reportedFailures.set(this.options.source, failure);
1216
+ this.options.onError?.(failure);
1217
+ }
1218
+ subscribeSource() {
1219
+ const source = this.options.source;
1220
+ this.subscription = source.store.subscribe(() => {
1221
+ if (this.disposed || this.options.source !== source) return;
1222
+ const next = source.store.get();
1223
+ const previousSnapshot = this.sourceState.snapshot;
1224
+ this.sourceState = next;
1225
+ if (previousSnapshot && next.snapshot && previousSnapshot.me !== next.snapshot.me) {
1226
+ this.epoch++;
1227
+ this.invalidControlledState = this.options.state;
1228
+ this.pending = null;
1229
+ this.localDrafts = EMPTY_DRAFTS;
1230
+ this.localActive = null;
1231
+ this.options.onDraftsChange?.({});
1232
+ this.options.onActiveInteractionChange?.(null);
1233
+ }
1234
+ this.reconcile();
1235
+ this.refresh();
1236
+ this.finishBarrier();
1237
+ this.reportSourceFailure();
1238
+ });
1239
+ }
1240
+ setOptions(next) {
1241
+ if (this.disposed) throw new Error("Game instance is disposed.");
1242
+ const changed = next.source !== this.options.source;
1243
+ if (changed) {
1244
+ this.epoch++;
1245
+ this.subscription?.unsubscribe();
1246
+ this.options.source.dispose();
1247
+ this.pending = null;
1248
+ }
1249
+ this.options = next;
1250
+ if (changed) {
1251
+ this.invalidControlledState = next.state;
1252
+ this.localDrafts = EMPTY_DRAFTS;
1253
+ this.localActive = null;
1254
+ this.sourceState = next.source.store.get();
1255
+ this.options.onDraftsChange?.({});
1256
+ this.options.onActiveInteractionChange?.(null);
1257
+ this.subscribeSource();
1258
+ }
1259
+ this.reconcile();
1260
+ this.refresh();
1261
+ this.reportSourceFailure();
1262
+ }
1263
+ reconcile() {
1264
+ const snapshot = this.sourceState.snapshot;
1265
+ if (!snapshot) return;
1266
+ const drafts = this.drafts();
1267
+ let changed = false;
1268
+ const next = { ...drafts };
1269
+ for (const [key, values] of Object.entries(drafts)) {
1270
+ if (!values) continue;
1271
+ const base = snapshot.frame.availableInteractions.find(
1272
+ (value) => value.interactionKey === key
1273
+ );
1274
+ if (!base) {
1275
+ if (this.pending?.key !== key) {
1276
+ delete next[key];
1277
+ changed = true;
1278
+ }
1279
+ continue;
1280
+ }
1281
+ const descriptor = this.descriptorFor(base, values);
1282
+ const kept = { ...values };
1283
+ let fieldChanged = false;
1284
+ for (const [name, value] of Object.entries(values)) {
1285
+ const input = descriptor.inputs.find((input2) => input2.key === name);
1286
+ if (!input && this.pending?.key === key) continue;
1287
+ const selection = input && inputSelection(input);
1288
+ if (input && selection?.mode === "many" && Array.isArray(value)) {
1289
+ const eligible = value.filter(
1290
+ (item) => inputTargetInDomain(input.domain, item)
1291
+ );
1292
+ const max = selection.max;
1293
+ const retained = max === void 0 ? eligible : eligible.slice(0, max);
1294
+ if (retained.length !== value.length) {
1295
+ kept[name] = retained;
1296
+ changed = true;
1297
+ fieldChanged = true;
1298
+ }
1299
+ continue;
1300
+ }
1301
+ if (!input || !inDomain(input, value, true)) {
1302
+ delete kept[name];
1303
+ changed = true;
1304
+ fieldChanged = true;
1305
+ }
1306
+ }
1307
+ if (fieldChanged) next[key] = immutableValues(kept);
1308
+ }
1309
+ if (changed) {
1310
+ const operation = this.pending;
1311
+ const submitted = operation?.draftRevision !== void 0 && (this.revisions.get(operation.key) ?? 0) === operation.draftRevision;
1312
+ this.writeDrafts(Object.freeze(next));
1313
+ if (submitted)
1314
+ operation.draftRevision = this.revisions.get(operation.key);
1315
+ }
1316
+ const active = this.active();
1317
+ if (active && !snapshot.frame.availableInteractions.some(
1318
+ (value) => value.interactionKey === active
1319
+ ))
1320
+ this.activate(null);
1321
+ }
1322
+ dispose() {
1323
+ if (this.disposed) return;
1324
+ this.disposed = true;
1325
+ this.featureDisposals.forEach((dispose) => dispose());
1326
+ this.subscription?.unsubscribe();
1327
+ this.pending = null;
1328
+ this.options.source.dispose();
1329
+ this.sourceState = this.options.source.store.get();
1330
+ this.refresh();
1331
+ }
1332
+ assertCoverage() {
1333
+ const missing = this.sourceState.snapshot?.frame.availableInteractions.filter(
1334
+ (d) => d.availability.status === "available" && !this.observed.has(d.interactionKey)
1335
+ ).map((d) => d.interactionKey) ?? [];
1336
+ if (missing.length)
1337
+ throw new Error(`Interactions not read: ${missing.join(", ")}`);
1338
+ }
1339
+ routeTarget(target, options) {
1340
+ if (target.kind === "card") {
1341
+ this.selectCard(target.value, options);
1342
+ return;
1343
+ }
1344
+ const candidates = this.lastInteractions.flatMap(
1345
+ (interaction) => interaction.getInputs().filter(
1346
+ (input) => (target.kind === "tile" ? input.descriptor.domain.type === "tileTarget" : matchesBoardTarget(input.descriptor, target)) && input.getIsEligible(target.value) && interaction.getIsAvailable() && (!options?.interaction || interaction.key === options.interaction) && (!options?.input || input.key === options.input)
1347
+ ).map((input) => ({ interaction, input }))
1348
+ );
1349
+ if (candidates.length > 1)
1350
+ throw new AmbiguousTargetError(JSON.stringify(target.value));
1351
+ const match = candidates[0];
1352
+ if (match)
1353
+ this.select(match.interaction.key, match.input.key, target.value);
1354
+ }
1355
+ prepareCardDrop(cards, target) {
1356
+ if (this.disposed || this.sourceState.request || this.pending || this.sourceState.connection !== "ready")
1357
+ return;
1358
+ const group = typeof cards !== "string";
1359
+ const cardIds = group ? cards : [cards];
1360
+ const cardId = cardIds[0];
1361
+ if (!cardId || new Set(cardIds).size !== cardIds.length) return;
1362
+ const card = this.store.get().cards.find(cardId);
1363
+ const interaction = card?.routes.find(
1364
+ (route) => route.key === target.interactionKey
1365
+ );
1366
+ if (!interaction?.currentLifetime() || !interaction.getIsAvailable())
1367
+ return;
1368
+ const cardInput = interaction.findInput(target.cardInputKey);
1369
+ if (!cardInput || cardInput.descriptor.domain.type !== "cardTarget" || (group ? !isManyInput(cardInput.descriptor) || !inputValueInDomain(
1370
+ cardInput.descriptor.domain,
1371
+ cardIds,
1372
+ cardInput.descriptor.domain.selection
1373
+ ) : !cardInput.getIsEligible(cardId)))
1374
+ return;
1375
+ const input = target.kind === "interaction" ? void 0 : interaction.findInput(target.inputKey);
1376
+ if (target.kind !== "interaction" && (!input || !(target.kind === "position" ? input.descriptor.domain.type === "zonePosition" : matchesBoardTarget(input.descriptor, target)) || !input.getIsEligible(target.value)))
1377
+ return;
1378
+ const params = target.kind === "interaction" ? target.params : void 0;
1379
+ const submitBound = group || params !== void 0 || target.kind === "position";
1380
+ if (params && Object.keys(params).some(
1381
+ (key) => key === cardInput.key || !interaction.findInput(key)
1382
+ ))
1383
+ return;
1384
+ let next = {
1385
+ ...this.drafts()[interaction.key],
1386
+ ...params
1387
+ };
1388
+ const chosen = next[cardInput.key];
1389
+ const alreadyChosen = !submitBound && !input && isManyInput(cardInput.descriptor) && Array.isArray(chosen) && chosen.includes(cardId);
1390
+ if (submitBound) {
1391
+ next[cardInput.key] = isManyInput(cardInput.descriptor) ? [...cardIds] : cardId;
1392
+ if (input && target.kind !== "interaction")
1393
+ next[input.key] = target.value;
1394
+ } else if (!alreadyChosen)
1395
+ routeCardInputIntent(
1396
+ {
1397
+ getDraft: () => next,
1398
+ setInput: (_key, key, value) => {
1399
+ next = { ...next, [key]: value };
1400
+ },
1401
+ clearInput: (_key, key) => {
1402
+ if (key) delete next[key];
1403
+ }
1404
+ },
1405
+ interaction.descriptor,
1406
+ {
1407
+ cardId,
1408
+ cardInputKey: cardInput.key,
1409
+ dropTarget: input && target.kind !== "interaction" ? { inputKey: input.key, value: target.value } : void 0
1410
+ }
1411
+ );
1412
+ if (submitBound && (!interaction.getIsAvailable(next) || !interaction.getIsReady(next)))
1413
+ return;
1414
+ return {
1415
+ interaction,
1416
+ next,
1417
+ alreadyChosen,
1418
+ submitBound
1419
+ };
1420
+ }
1421
+ routeCardDrop(cardId, target) {
1422
+ const prepared = this.prepareCardDrop(cardId, target);
1423
+ if (!prepared) return;
1424
+ const { interaction, next, alreadyChosen, submitBound } = prepared;
1425
+ if (!alreadyChosen || target.kind === "interaction" && target.params !== void 0)
1426
+ this.writeDraft(interaction.key, next);
1427
+ this.activate(interaction.key);
1428
+ const current = this.current(interaction.key);
1429
+ if (!alreadyChosen && current && (submitBound || shouldAutoSubmitInteraction(current.descriptor)) && current.getIsReady())
1430
+ this.handle(() => current.submit());
1431
+ }
1432
+ warnUnread() {
1433
+ const environment = import.meta.env?.DEV ?? globalThis.process?.env?.NODE_ENV === "development";
1434
+ if (!(this.options.debug ?? environment)) return;
1435
+ queueMicrotask(() => {
1436
+ if (this.disposed) return;
1437
+ for (const descriptor of this.sourceState.snapshot?.frame.availableInteractions ?? []) {
1438
+ const key = descriptor.interactionKey;
1439
+ if (descriptor.availability.status === "available" && !this.observed.has(key) && !this.warned.has(key)) {
1440
+ this.warned.add(key);
1441
+ console.warn(`Available interaction '${key}' has not been read.`);
1442
+ }
1443
+ }
1444
+ });
1445
+ }
1446
+ build(previous) {
1447
+ this.trackDrafts();
1448
+ const { snapshot, connection, request, failure } = this.sourceState;
1449
+ const old = previous?.snapshot;
1450
+ const drafts = this.drafts();
1451
+ const active = this.active();
1452
+ const sameSnapshot = snapshot === old;
1453
+ const sameActors = sameSnapshot;
1454
+ const summaries = snapshot?.players ?? [];
1455
+ const activeIds = snapshot?.frame.flow.activePlayers ?? [];
1456
+ const playerObjects = sameActors && previous ? previous.players.getAll() : Object.freeze(
1457
+ summaries.map(
1458
+ (player, index) => Object.freeze(
1459
+ new PlayerObject(
1460
+ this,
1461
+ player,
1462
+ index,
1463
+ player.playerId === snapshot?.me,
1464
+ activeIds.includes(player.playerId)
1465
+ )
1466
+ )
1467
+ )
1468
+ );
1469
+ const players = sameActors && previous ? previous.players : {
1470
+ order: Object.freeze(playerObjects.map((p) => p.id)),
1471
+ get: (id) => requireLookup(
1472
+ playerObjects.find((p) => p.id === id),
1473
+ "Player",
1474
+ id
1475
+ ),
1476
+ find: (id) => playerObjects.find((p) => p.id === id),
1477
+ getAll: () => playerObjects,
1478
+ next: (id) => {
1479
+ const index = playerObjects.findIndex((p) => p.id === id);
1480
+ return requireLookup(
1481
+ index < 0 ? void 0 : playerObjects[(index + 1) % playerObjects.length],
1482
+ "Next player after",
1483
+ id
1484
+ );
1485
+ }
1486
+ };
1487
+ const me = sameActors && previous ? previous.me : snapshot ? {
1488
+ id: snapshot.me,
1489
+ player: players.get(snapshot.me),
1490
+ getCanAct: () => snapshot.frame.availableInteractions.some(
1491
+ (value) => value.availability.status === "available"
1492
+ )
1493
+ } : null;
1494
+ const descriptors = snapshot?.frame.availableInteractions ?? [];
1495
+ const interactionObjects = descriptors.map((base) => {
1496
+ const key = base.interactionKey;
1497
+ const draft = drafts[key] ?? EMPTY;
1498
+ const descriptor = this.descriptorFor(base, draft);
1499
+ const status = request || this.pending ? request?.interactionId === descriptor.interactionId && request.phase === "awaiting-frame" ? "submitted" : "submitting" : "open";
1500
+ const oldObject = this.lastInteractions.find(
1501
+ (value) => value.key === key
1502
+ );
1503
+ return oldObject?.descriptor === descriptor && oldObject.epoch === this.epoch && oldObject.source === this.options.source && oldObject.draftIdentity === draft && oldObject.status === status && oldObject.connected === (connection === "ready") ? oldObject : this.object(
1504
+ "interaction",
1505
+ new InteractionObject(
1506
+ this,
1507
+ descriptor,
1508
+ draft,
1509
+ status,
1510
+ connection === "ready"
1511
+ )
1512
+ );
1513
+ });
1514
+ const interactionsSame = previous && interactionObjects.length === this.lastInteractions.length && interactionObjects.every(
1515
+ (value, index) => value === this.lastInteractions[index]
1516
+ );
1517
+ this.lastInteractions = Object.freeze(interactionObjects);
1518
+ const interactions = interactionsSame ? previous.interactions : {
1519
+ get: (key) => {
1520
+ this.observed.add(key);
1521
+ return requireLookup(
1522
+ interactionObjects.find((value) => value.key === key),
1523
+ "Interaction",
1524
+ key
1525
+ );
1526
+ },
1527
+ find: (key) => {
1528
+ this.observed.add(key);
1529
+ return interactionObjects.find((value) => value.key === key);
1530
+ },
1531
+ list: () => {
1532
+ interactionObjects.forEach((value) => this.observed.add(value.key));
1533
+ return Object.freeze(interactionObjects);
1534
+ },
1535
+ listAvailable: () => {
1536
+ const available = interactionObjects.filter(
1537
+ (value) => value.getIsAvailable()
1538
+ );
1539
+ available.forEach((value) => this.observed.add(value.key));
1540
+ return Object.freeze(available);
1541
+ }
1542
+ };
1543
+ const inputs = interactionsSame && previous ? previous.inputs : {
1544
+ get: (key, name) => requireLookup(
1545
+ interactions.find(key)?.findInput(name),
1546
+ `Input in interaction ${key}`,
1547
+ name
1548
+ ),
1549
+ find: (key, name) => interactions.find(key)?.findInput(name)
1550
+ };
1551
+ const zonesSame = sameSnapshot && interactionsSame && previous;
1552
+ const zones = zonesSame ? previous.zones : (() => {
1553
+ const objects = Object.entries(snapshot?.frame.zones ?? {}).flatMap(
1554
+ ([id, hosts]) => Object.entries(hosts).map(
1555
+ ([hostId, zone]) => this.object(
1556
+ "zone",
1557
+ new ZoneObject(
1558
+ this,
1559
+ id,
1560
+ hostId,
1561
+ Object.freeze(
1562
+ zone.cardIds.map(
1563
+ (cardId, index) => this.object(
1564
+ "card",
1565
+ new CardObject(
1566
+ this,
1567
+ cardId,
1568
+ id,
1569
+ hostId,
1570
+ index,
1571
+ zone.cardViewsById[cardId],
1572
+ zone.cardBacksById[cardId],
1573
+ Object.freeze(
1574
+ (zone.playableByCardId[cardId] ?? []).map(
1575
+ (descriptor) => this.object(
1576
+ "interaction",
1577
+ new InteractionObject(
1578
+ this,
1579
+ descriptor,
1580
+ drafts[descriptor.interactionKey] ?? EMPTY,
1581
+ request || this.pending ? "submitting" : "open",
1582
+ connection === "ready"
1583
+ )
1584
+ )
1585
+ )
1586
+ )
1587
+ )
1588
+ )
1589
+ )
1590
+ ),
1591
+ Object.freeze(
1592
+ zone.tiles.map(
1593
+ (tile, index) => new TileObject(
1594
+ this,
1595
+ tile,
1596
+ id,
1597
+ hostId,
1598
+ index,
1599
+ Object.freeze(
1600
+ interactionObjects.filter(
1601
+ (interaction) => interaction.getInputs().some(
1602
+ (input) => input.descriptor.domain.type === "tileTarget" && input.getIsEligible(tile.ref)
1603
+ )
1604
+ )
1605
+ )
1606
+ )
1607
+ )
1608
+ )
1609
+ )
1610
+ )
1611
+ )
1612
+ );
1613
+ const find = (id, hostId) => objects.find((zone) => zone.id === id && zone.hostId === hostId);
1614
+ return {
1615
+ get: (id, hostId) => requireLookup(find(id, hostId), "Zone", id),
1616
+ find,
1617
+ getAll: () => Object.freeze(objects)
1618
+ };
1619
+ })();
1620
+ const cards = zonesSame ? previous.cards : {
1621
+ get: (id) => requireLookup(
1622
+ zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0),
1623
+ "Card",
1624
+ id
1625
+ ),
1626
+ find: (id) => zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0)
1627
+ };
1628
+ const capturedState = this.lastSnapshotDrafts === drafts && previous?.state.activeInteraction === active ? previous.state : immutableCopy({ drafts, activeInteraction: active });
1629
+ this.lastSnapshotDrafts = drafts;
1630
+ for (const value of [players, me, interactions, inputs, zones, cards])
1631
+ if (value) Object.freeze(value);
1632
+ const model = {
1633
+ snapshot,
1634
+ view: snapshot?.frame.view ?? null,
1635
+ version: snapshot?.version ?? null,
1636
+ connection,
1637
+ request,
1638
+ failure,
1639
+ state: capturedState,
1640
+ phase: sameSnapshot && previous ? previous.phase : Object.freeze(
1641
+ new PhaseObject(snapshot?.frame.flow.currentPhase ?? null)
1642
+ ),
1643
+ turn: sameSnapshot && previous ? previous.turn : Object.freeze({
1644
+ activePlayerIds: activeIds,
1645
+ currentPlayerId: activeIds.length === 1 ? activeIds[0] : null,
1646
+ order: players.order,
1647
+ isMine: snapshot ? activeIds.includes(snapshot.me) : false
1648
+ }),
1649
+ me,
1650
+ players,
1651
+ interactions,
1652
+ inputs,
1653
+ zones,
1654
+ cards,
1655
+ events: sameSnapshot && previous ? previous.events : Object.freeze({
1656
+ recent: snapshot?.frame.events ?? Object.freeze([])
1657
+ })
1658
+ };
1659
+ this.building = model;
1660
+ try {
1661
+ for (const [key, descriptor] of this.rootProperties)
1662
+ Object.defineProperty(model, key, {
1663
+ value: descriptor.get ? descriptor.get.call(this.instance) : descriptor.value,
1664
+ enumerable: true
1665
+ });
1666
+ } finally {
1667
+ this.building = void 0;
1668
+ }
1669
+ return model;
1670
+ }
1671
+ refresh() {
1672
+ if (!this.store) return;
1673
+ this.store.setState((previous) => Object.freeze(this.build(previous)));
1674
+ this.warnUnread();
1675
+ }
1676
+ };
1677
+ function createGameInstance() {
1678
+ return function(options) {
1679
+ const controller = new Controller(
1680
+ options,
1681
+ options.features
1682
+ );
1683
+ return controller.instance;
1684
+ };
1685
+ }
1686
+
1687
+ // src/headless/gesture.ts
1688
+ var GESTURE_THRESHOLDS = Object.freeze({
1689
+ /** Movement, in CSS pixels, before a press becomes a drag or a browse. */
1690
+ slop: 8,
1691
+ /** A touch or pen held this long without moving inspects the card. */
1692
+ holdMs: 350,
1693
+ /** A mouse resting on a card this long inspects it. */
1694
+ hoverMs: 250,
1695
+ /** A finger drags when upward travel exceeds this multiple of sideways travel. */
1696
+ axisBias: 1.25
1697
+ });
1698
+ function createGestureRecognizer(down, callbacks, options = {}) {
1699
+ const origin = { x: down.clientX, y: down.clientY };
1700
+ const mouse = down.pointerType === "mouse";
1701
+ let kind = "tap";
1702
+ let finished = false;
1703
+ let pull = origin;
1704
+ const holdTimer = mouse ? void 0 : setTimeout(() => {
1705
+ kind = "hold";
1706
+ callbacks.hold();
1707
+ }, GESTURE_THRESHOLDS.holdMs);
1708
+ const point = (event) => ({
1709
+ x: event.clientX,
1710
+ y: event.clientY
1711
+ });
1712
+ const ours = (event) => !finished && (!event || event.pointerId === down.pointerId);
1713
+ function finish() {
1714
+ finished = true;
1715
+ clearTimeout(holdTimer);
1716
+ }
1717
+ const rises = (from, to) => from.y - to.y > GESTURE_THRESHOLDS.axisBias * Math.abs(to.x - from.x);
1718
+ function drag(from, to) {
1719
+ if (!callbacks.dragStart(from)) return false;
1720
+ kind = "drag";
1721
+ callbacks.dragMove(to);
1722
+ return true;
1723
+ }
1724
+ return {
1725
+ move(event) {
1726
+ if (!ours(event)) return;
1727
+ const at = point(event);
1728
+ if (kind === "drag") return callbacks.dragMove(at);
1729
+ if (kind === "browse") {
1730
+ if (!options.scrub) return;
1731
+ callbacks.browse?.(at);
1732
+ if (!rises(pull, at) || pull.y - at.y >= GESTURE_THRESHOLDS.slop && !drag(pull, at))
1733
+ pull = at;
1734
+ return;
1735
+ }
1736
+ if (kind !== "tap" || Math.hypot(at.x - origin.x, at.y - origin.y) < GESTURE_THRESHOLDS.slop)
1737
+ return;
1738
+ clearTimeout(holdTimer);
1739
+ const lifts = mouse || options.dragDirection === "any" || rises(origin, at);
1740
+ if (lifts && drag(origin, at)) return;
1741
+ kind = "browse";
1742
+ pull = at;
1743
+ if (options.scrub) callbacks.browse?.(at);
1744
+ },
1745
+ up(event) {
1746
+ if (!ours(event)) return;
1747
+ finish();
1748
+ callbacks.end(kind, point(event));
1749
+ },
1750
+ cancel(event) {
1751
+ if (!ours(event)) return;
1752
+ finish();
1753
+ callbacks.cancel(kind);
1754
+ }
1755
+ };
1756
+ }
1757
+ function magneticDropPoint(point, box, retained, coarse) {
1758
+ const radius = coarse ? retained ? 52 : 28 : retained ? 36 : 18;
1759
+ if (box.width <= 0 || box.height <= 0) return null;
1760
+ const x = Math.max(box.x, Math.min(point.x, box.x + box.width));
1761
+ const y = Math.max(box.y, Math.min(point.y, box.y + box.height));
1762
+ return Math.hypot(point.x - x, point.y - y) <= radius ? { x, y } : null;
1763
+ }
1764
+
1765
+ // src/headless/drop-targets.ts
1766
+ function isSameDropTarget(left, right) {
1767
+ if (left.interactionKey !== right.interactionKey || left.cardInputKey !== right.cardInputKey)
1768
+ return false;
1769
+ if (left.kind === "interaction" || right.kind === "interaction")
1770
+ return left.kind === "interaction" && right.kind === "interaction" && sameParams(left.params, right.params);
1771
+ if (left.kind === "position" || right.kind === "position")
1772
+ return left.kind === "position" && right.kind === "position" && left.inputKey === right.inputKey && inputValueKey(left.value) === inputValueKey(right.value);
1773
+ 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.inputKey === right.inputKey;
1774
+ }
1775
+ function sameParams(left, right) {
1776
+ if (left === void 0 || right === void 0) return left === right;
1777
+ const keys = Object.keys(left);
1778
+ return keys.length === Object.keys(right).length && keys.every(
1779
+ (key) => key in right && inputValueKey(left[key]) === inputValueKey(right[key])
1780
+ );
1781
+ }
1782
+
1783
+ export {
1784
+ runtimeFeatures,
1785
+ AmbiguousTargetError,
1786
+ createGameInstance,
1787
+ isSameDropTarget,
1788
+ GESTURE_THRESHOLDS,
1789
+ createGestureRecognizer,
1790
+ magneticDropPoint
1791
+ };
1792
+ //# sourceMappingURL=chunk-3RFGPBES.js.map