@dreamboard-games/sdk 0.5.0-alpha.1 → 0.5.0-alpha.11

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