@dreamboard-games/sdk 0.5.0-alpha.0 → 0.5.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +388 -24
  2. package/dist/board-element-CRN_hj5F.d.ts +13 -0
  3. package/dist/chunk-34IGUWOP.js +1703 -0
  4. package/dist/chunk-34IGUWOP.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 +4748 -1
  16. package/dist/index.js +1556 -7
  17. package/dist/index.js.map +1 -1
  18. package/dist/model-BeslAXVA.d.ts +648 -0
  19. package/dist/react.d.ts +78 -0
  20. package/dist/react.js +411 -0
  21. package/dist/react.js.map +1 -0
  22. package/dist/reducer.d.ts +786 -544
  23. package/dist/reducer.js +4286 -842
  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-materialize-cli.d.ts +0 -1
  41. package/dist/authoring-materialize-cli.js +0 -45
  42. package/dist/authoring-materialize-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-2GQIALJL.js +0 -219
  47. package/dist/chunk-2GQIALJL.js.map +0 -1
  48. package/dist/chunk-533O7C4L.js +0 -7095
  49. package/dist/chunk-533O7C4L.js.map +0 -1
  50. package/dist/chunk-6LMESVGT.js +0 -5960
  51. package/dist/chunk-6LMESVGT.js.map +0 -1
  52. package/dist/chunk-DQS2EG5E.js +0 -564
  53. package/dist/chunk-DQS2EG5E.js.map +0 -1
  54. package/dist/chunk-EOEA3I64.js +0 -1864
  55. package/dist/chunk-EOEA3I64.js.map +0 -1
  56. package/dist/chunk-FL67RDID.js +0 -2903
  57. package/dist/chunk-FL67RDID.js.map +0 -1
  58. package/dist/chunk-HIH6Y4FA.js +0 -7802
  59. package/dist/chunk-HIH6Y4FA.js.map +0 -1
  60. package/dist/chunk-HUK4234C.js +0 -166
  61. package/dist/chunk-HUK4234C.js.map +0 -1
  62. package/dist/chunk-KGV2EUNN.js +0 -532
  63. package/dist/chunk-KGV2EUNN.js.map +0 -1
  64. package/dist/chunk-KRJPRYEA.js +0 -922
  65. package/dist/chunk-KRJPRYEA.js.map +0 -1
  66. package/dist/chunk-KWHOFBDM.js +0 -9
  67. package/dist/chunk-KWHOFBDM.js.map +0 -1
  68. package/dist/chunk-LMHCGCA6.js +0 -104
  69. package/dist/chunk-LMHCGCA6.js.map +0 -1
  70. package/dist/chunk-N33CGX2F.js +0 -263
  71. package/dist/chunk-N33CGX2F.js.map +0 -1
  72. package/dist/chunk-PF7L4BMG.js +0 -117
  73. package/dist/chunk-PF7L4BMG.js.map +0 -1
  74. package/dist/chunk-PZ5AY32C.js +0 -10
  75. package/dist/chunk-PZ5AY32C.js.map +0 -1
  76. package/dist/chunk-T3ZKNUZ7.js +0 -1
  77. package/dist/chunk-T3ZKNUZ7.js.map +0 -1
  78. package/dist/chunk-TXELBVTG.js +0 -32
  79. package/dist/chunk-TXELBVTG.js.map +0 -1
  80. package/dist/chunk-VDXOF4FW.js +0 -69
  81. package/dist/chunk-VDXOF4FW.js.map +0 -1
  82. package/dist/chunk-VRRR6QZT.js +0 -11941
  83. package/dist/chunk-VRRR6QZT.js.map +0 -1
  84. package/dist/chunk-VWTKGIYF.js +0 -90
  85. package/dist/chunk-VWTKGIYF.js.map +0 -1
  86. package/dist/chunk-WHR5UW3F.js +0 -1988
  87. package/dist/chunk-WHR5UW3F.js.map +0 -1
  88. package/dist/chunk-XFMSLL3P.js +0 -637
  89. package/dist/chunk-XFMSLL3P.js.map +0 -1
  90. package/dist/chunk-ZSTCUJKZ.js +0 -21
  91. package/dist/chunk-ZSTCUJKZ.js.map +0 -1
  92. package/dist/codegen.d.ts +0 -941
  93. package/dist/codegen.js +0 -44
  94. package/dist/codegen.js.map +0 -1
  95. package/dist/components-CNAt5J1p.d.ts +0 -1300
  96. package/dist/definitions-bZSBaPjj.d.ts +0 -300
  97. package/dist/diagnostics-DIi3ina7.d.ts +0 -50
  98. package/dist/digest.d.ts +0 -8
  99. package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
  100. package/dist/index-CR69MyWr.d.ts +0 -193
  101. package/dist/index.d-Bw8AOcgp.d.ts +0 -2036
  102. package/dist/package-set.d.ts +0 -13
  103. package/dist/package-set.js +0 -13
  104. package/dist/package-set.js.map +0 -1
  105. package/dist/player-state-Cqpyeql0.d.ts +0 -371
  106. package/dist/plugin-runtime-contract.d.ts +0 -17
  107. package/dist/plugin-runtime-contract.js +0 -94
  108. package/dist/plugin-runtime-contract.js.map +0 -1
  109. package/dist/plugin-state-BRH3UiUp.d.ts +0 -214
  110. package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
  111. package/dist/protocol-CVaSqPgX.d.ts +0 -320
  112. package/dist/reducer/advanced.d.ts +0 -81
  113. package/dist/reducer/advanced.js +0 -45
  114. package/dist/reducer/advanced.js.map +0 -1
  115. package/dist/reducer-contract.d.ts +0 -3
  116. package/dist/reducer-contract.js +0 -16
  117. package/dist/reducer-contract.js.map +0 -1
  118. package/dist/reference-games/index.d.ts +0 -31
  119. package/dist/reference-games/index.js +0 -48
  120. package/dist/reference-games/index.js.map +0 -1
  121. package/dist/runtime/primitives.d.ts +0 -250
  122. package/dist/runtime/primitives.js +0 -189
  123. package/dist/runtime/primitives.js.map +0 -1
  124. package/dist/runtime/runtime-api.d.ts +0 -2
  125. package/dist/runtime/runtime-api.js +0 -1
  126. package/dist/runtime/runtime-api.js.map +0 -1
  127. package/dist/runtime/workspace-contract.d.ts +0 -359
  128. package/dist/runtime/workspace-contract.js +0 -23
  129. package/dist/runtime/workspace-contract.js.map +0 -1
  130. package/dist/runtime-C7Z2acNU.d.ts +0 -1706
  131. package/dist/runtime-api-CgPuEVeF.d.ts +0 -86
  132. package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
  133. package/dist/runtime.d.ts +0 -83
  134. package/dist/runtime.js +0 -240
  135. package/dist/runtime.js.map +0 -1
  136. package/dist/schema.d.ts +0 -405
  137. package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
  138. package/dist/testing-compiler.d.ts +0 -37
  139. package/dist/testing-compiler.js +0 -272
  140. package/dist/testing-compiler.js.map +0 -1
  141. package/dist/testing-runtime.d.ts +0 -119
  142. package/dist/testing-runtime.js +0 -212
  143. package/dist/testing-runtime.js.map +0 -1
  144. package/dist/types-DJj5MJkl.d.ts +0 -256
  145. package/dist/types-DN4O7kH_.d.ts +0 -122
  146. package/dist/types-q1Guidm5.d.ts +0 -28
  147. package/dist/types.d.ts +0 -1958
  148. package/dist/types.js +0 -26
  149. package/dist/types.js.map +0 -1
  150. package/dist/ui/components.d.ts +0 -18
  151. package/dist/ui/components.js +0 -220
  152. package/dist/ui/components.js.map +0 -1
  153. package/dist/ui/defaults.d.ts +0 -19
  154. package/dist/ui/defaults.js +0 -104
  155. package/dist/ui/defaults.js.map +0 -1
  156. package/dist/ui/player-state.d.ts +0 -2
  157. package/dist/ui/player-state.js +0 -1
  158. package/dist/ui/player-state.js.map +0 -1
  159. package/dist/ui/plugin-styles.css +0 -2
  160. package/dist/ui-contract-BC4JpysE.d.ts +0 -1169
  161. package/dist/ui.d.ts +0 -318
  162. package/dist/ui.js +0 -281
  163. package/dist/ui.js.map +0 -1
  164. package/dist/views-D2Cfzr3h.d.ts +0 -1588
@@ -0,0 +1,1703 @@
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
+
1686
+ // src/headless/drop-targets.ts
1687
+ function isSameDropTarget(left, right) {
1688
+ if (left.interactionKey !== right.interactionKey || left.cardInputKey !== right.cardInputKey)
1689
+ return false;
1690
+ if (left.kind === "interaction" || right.kind === "interaction")
1691
+ return left.kind === right.kind;
1692
+ 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;
1693
+ }
1694
+
1695
+ export {
1696
+ runtimeFeatures,
1697
+ AmbiguousTargetError,
1698
+ createGameInstance,
1699
+ isSameDropTarget,
1700
+ GESTURE_THRESHOLDS,
1701
+ createGestureRecognizer
1702
+ };
1703
+ //# sourceMappingURL=chunk-34IGUWOP.js.map