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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +462 -53
  2. package/dist/board-element-CqtxU5VZ.d.ts +13 -0
  3. package/dist/chunk-3RFGPBES.js +1792 -0
  4. package/dist/chunk-3RFGPBES.js.map +1 -0
  5. package/dist/chunk-CZRPUWCT.js +1602 -0
  6. package/dist/chunk-CZRPUWCT.js.map +1 -0
  7. package/dist/chunk-DMSXVMYY.js +7199 -0
  8. package/dist/chunk-DMSXVMYY.js.map +1 -0
  9. package/dist/chunk-U6R7FAH4.js +630 -0
  10. package/dist/chunk-U6R7FAH4.js.map +1 -0
  11. package/dist/chunk-YT3RPO5J.js +635 -0
  12. package/dist/chunk-YT3RPO5J.js.map +1 -0
  13. package/dist/diagnostics-ZAPEBlJP.d.ts +64 -0
  14. package/dist/extract-CwJWDrSD.d.ts +6850 -0
  15. package/dist/index.d.ts +5177 -1
  16. package/dist/index.js +1990 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/react.d.ts +158 -0
  19. package/dist/react.js +1119 -0
  20. package/dist/react.js.map +1 -0
  21. package/dist/reducer.d.ts +1028 -97
  22. package/dist/reducer.js +4892 -125
  23. package/dist/reducer.js.map +1 -1
  24. package/dist/shortcuts-B_RrufOM.d.ts +822 -0
  25. package/dist/testing.d.ts +529 -569
  26. package/dist/testing.js +2588 -934
  27. package/dist/testing.js.map +1 -1
  28. package/dist/types-BxXRINRF.d.ts +118 -0
  29. package/package.json +39 -144
  30. package/dist/HandView-Bi2lxzQP.d.ts +0 -79
  31. package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
  32. package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
  33. package/dist/attributes-DbvyMbXw.d.ts +0 -68
  34. package/dist/browser-interaction.d.ts +0 -910
  35. package/dist/browser-interaction.js +0 -119
  36. package/dist/browser-interaction.js.map +0 -1
  37. package/dist/chunk-32PFKDV7.js +0 -11705
  38. package/dist/chunk-32PFKDV7.js.map +0 -1
  39. package/dist/chunk-3BAD4VC2.js +0 -263
  40. package/dist/chunk-3BAD4VC2.js.map +0 -1
  41. package/dist/chunk-3SKDNDPG.js +0 -104
  42. package/dist/chunk-3SKDNDPG.js.map +0 -1
  43. package/dist/chunk-5YV2WHJ4.js +0 -25
  44. package/dist/chunk-5YV2WHJ4.js.map +0 -1
  45. package/dist/chunk-ANRB4XKA.js +0 -90
  46. package/dist/chunk-ANRB4XKA.js.map +0 -1
  47. package/dist/chunk-BI4G3PM2.js +0 -1864
  48. package/dist/chunk-BI4G3PM2.js.map +0 -1
  49. package/dist/chunk-EVCLTY4Q.js +0 -935
  50. package/dist/chunk-EVCLTY4Q.js.map +0 -1
  51. package/dist/chunk-GDPKTBUR.js +0 -564
  52. package/dist/chunk-GDPKTBUR.js.map +0 -1
  53. package/dist/chunk-H6VDGFL5.js +0 -2903
  54. package/dist/chunk-H6VDGFL5.js.map +0 -1
  55. package/dist/chunk-LR3ZTWQF.js +0 -5960
  56. package/dist/chunk-LR3ZTWQF.js.map +0 -1
  57. package/dist/chunk-PF7L4BMG.js +0 -117
  58. package/dist/chunk-PF7L4BMG.js.map +0 -1
  59. package/dist/chunk-PN5O6GG2.js +0 -32
  60. package/dist/chunk-PN5O6GG2.js.map +0 -1
  61. package/dist/chunk-PZ5AY32C.js +0 -10
  62. package/dist/chunk-PZ5AY32C.js.map +0 -1
  63. package/dist/chunk-S2JEDVWH.js +0 -7165
  64. package/dist/chunk-S2JEDVWH.js.map +0 -1
  65. package/dist/chunk-T3ZKNUZ7.js +0 -1
  66. package/dist/chunk-T3ZKNUZ7.js.map +0 -1
  67. package/dist/chunk-UGH54WZ2.js +0 -223
  68. package/dist/chunk-UGH54WZ2.js.map +0 -1
  69. package/dist/chunk-VCDWP5VL.js +0 -746
  70. package/dist/chunk-VCDWP5VL.js.map +0 -1
  71. package/dist/chunk-VDXOF4FW.js +0 -69
  72. package/dist/chunk-VDXOF4FW.js.map +0 -1
  73. package/dist/chunk-WHR5UW3F.js +0 -1988
  74. package/dist/chunk-WHR5UW3F.js.map +0 -1
  75. package/dist/chunk-Y75CFE77.js +0 -17
  76. package/dist/chunk-Y75CFE77.js.map +0 -1
  77. package/dist/chunk-Z7QCREWI.js +0 -3435
  78. package/dist/chunk-Z7QCREWI.js.map +0 -1
  79. package/dist/chunk-ZZGN2ATS.js +0 -1941
  80. package/dist/chunk-ZZGN2ATS.js.map +0 -1
  81. package/dist/components-gAbIz7hK.d.ts +0 -1284
  82. package/dist/definitions-BgmV_GhC.d.ts +0 -300
  83. package/dist/diagnostics-DIi3ina7.d.ts +0 -50
  84. package/dist/digest.d.ts +0 -8
  85. package/dist/game-CsZScpcn.d.ts +0 -763
  86. package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
  87. package/dist/index-CcT9Q7N7.d.ts +0 -193
  88. package/dist/index.d-DjzoK7zn.d.ts +0 -2101
  89. package/dist/package-set.d.ts +0 -13
  90. package/dist/package-set.js +0 -12
  91. package/dist/package-set.js.map +0 -1
  92. package/dist/player-state-Cqpyeql0.d.ts +0 -371
  93. package/dist/plugin-runtime-contract.d.ts +0 -17
  94. package/dist/plugin-runtime-contract.js +0 -92
  95. package/dist/plugin-runtime-contract.js.map +0 -1
  96. package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
  97. package/dist/protocol-dYgafTYY.d.ts +0 -314
  98. package/dist/reducer/advanced.d.ts +0 -82
  99. package/dist/reducer/advanced.js +0 -51
  100. package/dist/reducer/advanced.js.map +0 -1
  101. package/dist/reducer-contract.d.ts +0 -11
  102. package/dist/reducer-contract.js +0 -16
  103. package/dist/reducer-contract.js.map +0 -1
  104. package/dist/reference-games/index.d.ts +0 -31
  105. package/dist/reference-games/index.js +0 -48
  106. package/dist/reference-games/index.js.map +0 -1
  107. package/dist/runtime/primitives.d.ts +0 -250
  108. package/dist/runtime/primitives.js +0 -189
  109. package/dist/runtime/primitives.js.map +0 -1
  110. package/dist/runtime/runtime-api.d.ts +0 -2
  111. package/dist/runtime/runtime-api.js +0 -1
  112. package/dist/runtime/runtime-api.js.map +0 -1
  113. package/dist/runtime/workspace-contract.d.ts +0 -392
  114. package/dist/runtime/workspace-contract.js +0 -28
  115. package/dist/runtime/workspace-contract.js.map +0 -1
  116. package/dist/runtime-RJ5orDJU.d.ts +0 -1699
  117. package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
  118. package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
  119. package/dist/runtime.d.ts +0 -83
  120. package/dist/runtime.js +0 -242
  121. package/dist/runtime.js.map +0 -1
  122. package/dist/schema.d.ts +0 -379
  123. package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
  124. package/dist/testing-compiler.d.ts +0 -37
  125. package/dist/testing-compiler.js +0 -272
  126. package/dist/testing-compiler.js.map +0 -1
  127. package/dist/testing-runtime.d.ts +0 -119
  128. package/dist/testing-runtime.js +0 -212
  129. package/dist/testing-runtime.js.map +0 -1
  130. package/dist/types-DJj5MJkl.d.ts +0 -256
  131. package/dist/types-DR7DoB1x.d.ts +0 -28
  132. package/dist/types-JWCYHmu7.d.ts +0 -122
  133. package/dist/types.d.ts +0 -1958
  134. package/dist/types.js +0 -14
  135. package/dist/types.js.map +0 -1
  136. package/dist/ui/components.d.ts +0 -17
  137. package/dist/ui/components.js +0 -216
  138. package/dist/ui/components.js.map +0 -1
  139. package/dist/ui/defaults.d.ts +0 -19
  140. package/dist/ui/defaults.js +0 -104
  141. package/dist/ui/defaults.js.map +0 -1
  142. package/dist/ui/player-state.d.ts +0 -2
  143. package/dist/ui/player-state.js +0 -1
  144. package/dist/ui/player-state.js.map +0 -1
  145. package/dist/ui/plugin-styles.css +0 -2
  146. package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
  147. package/dist/ui.d.ts +0 -317
  148. package/dist/ui.js +0 -277
  149. package/dist/ui.js.map +0 -1
  150. package/dist/views-BpuWcOyN.d.ts +0 -1641
@@ -0,0 +1,1602 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/shared/domain/per-player-instance.ts
8
+ import * as z from "zod";
9
+ var GENERATED_ID_PREFIX = "@db/";
10
+ var instanceTuple = z.tuple([
11
+ z.enum(["board", "card", "piece", "die", "tile"]),
12
+ z.string().min(1),
13
+ z.string().min(1)
14
+ ]);
15
+ function perPlayerInstanceId(family, baseId, playerId) {
16
+ instanceTuple.parse([family, baseId, playerId]);
17
+ return `${GENERATED_ID_PREFIX}${JSON.stringify([family, baseId, playerId])}`;
18
+ }
19
+ function parsePerPlayerInstanceId(value) {
20
+ if (typeof value !== "string" || !value.startsWith(GENERATED_ID_PREFIX))
21
+ return null;
22
+ let candidate;
23
+ try {
24
+ candidate = JSON.parse(value.slice(GENERATED_ID_PREFIX.length));
25
+ } catch {
26
+ return null;
27
+ }
28
+ const parsed = instanceTuple.safeParse(candidate);
29
+ if (!parsed.success) return null;
30
+ const [family, baseId, playerId] = parsed.data;
31
+ const id = perPlayerInstanceId(family, baseId, playerId);
32
+ if (id !== value) return null;
33
+ return { id, family, baseId, playerId };
34
+ }
35
+ function perPlayerInstanceSchema(family, bases) {
36
+ const known = new Set(bases);
37
+ return z.custom(
38
+ (value) => {
39
+ const parsed = parsePerPlayerInstanceId(value);
40
+ return parsed?.family === family && known.has(parsed.baseId);
41
+ },
42
+ { error: "Expected a canonical instance of a declared family and base." }
43
+ );
44
+ }
45
+
46
+ // src/shared/domain/board-element.ts
47
+ import * as z2 from "zod";
48
+ var elementTuple = z2.tuple([
49
+ z2.enum(["board-edge", "board-vertex"]),
50
+ z2.enum(["hex", "square"]),
51
+ z2.string().min(1),
52
+ z2.string().min(1)
53
+ ]);
54
+ function validLattice(kind, layout, value) {
55
+ const pattern = layout === "hex" ? kind === "board-edge" ? /^(-?\d+),(-?\d+):e[012]$/ : /^(-?\d+),(-?\d+):v[01]$/ : kind === "board-edge" ? /^(-?\d+),(-?\d+):[hv]$/ : /^(-?\d+),(-?\d+)$/;
56
+ const match = pattern.exec(value);
57
+ return match !== null && [match[1], match[2]].every(
58
+ (part) => Number.isSafeInteger(Number(part)) && String(Number(part)) === part
59
+ );
60
+ }
61
+ function encode(kind, layout, boardId, latticeId) {
62
+ elementTuple.parse([kind, layout, boardId, latticeId]);
63
+ if (!validLattice(kind, layout, latticeId))
64
+ throw new Error("Invalid canonical board lattice identity.");
65
+ return `${GENERATED_ID_PREFIX}${JSON.stringify([kind, layout, boardId, latticeId])}`;
66
+ }
67
+ function boardEdgeId(layout, boardId, latticeId) {
68
+ return encode(
69
+ "board-edge",
70
+ layout,
71
+ boardId,
72
+ latticeId
73
+ );
74
+ }
75
+ function boardVertexId(layout, boardId, latticeId) {
76
+ return encode(
77
+ "board-vertex",
78
+ layout,
79
+ boardId,
80
+ latticeId
81
+ );
82
+ }
83
+ function parseBoardElementId(value) {
84
+ if (typeof value !== "string" || !value.startsWith(GENERATED_ID_PREFIX))
85
+ return null;
86
+ let candidate;
87
+ try {
88
+ candidate = JSON.parse(value.slice(GENERATED_ID_PREFIX.length));
89
+ } catch {
90
+ return null;
91
+ }
92
+ const parsed = elementTuple.safeParse(candidate);
93
+ if (!parsed.success) return null;
94
+ const [kind, layout, boardId, latticeId] = parsed.data;
95
+ if (!validLattice(kind, layout, latticeId) || encode(kind, layout, boardId, latticeId) !== value)
96
+ return null;
97
+ return {
98
+ kind: kind === "board-edge" ? "edge" : "vertex",
99
+ layout,
100
+ boardId,
101
+ latticeId
102
+ };
103
+ }
104
+
105
+ // src/shared/domain/tile-space.ts
106
+ import * as z3 from "zod";
107
+ var tileSpaceTuple = z3.tuple([
108
+ z3.literal("tile-space"),
109
+ z3.string().min(1),
110
+ z3.string().min(1)
111
+ ]);
112
+ function tileSpaceId(tileId, cellId) {
113
+ tileSpaceTuple.parse(["tile-space", tileId, cellId]);
114
+ return `${GENERATED_ID_PREFIX}${JSON.stringify(["tile-space", tileId, cellId])}`;
115
+ }
116
+ function parseTileSpaceId(value) {
117
+ if (typeof value !== "string" || !value.startsWith(GENERATED_ID_PREFIX))
118
+ return null;
119
+ let candidate;
120
+ try {
121
+ candidate = JSON.parse(value.slice(GENERATED_ID_PREFIX.length));
122
+ } catch {
123
+ return null;
124
+ }
125
+ const parsed = tileSpaceTuple.safeParse(candidate);
126
+ if (!parsed.success) return null;
127
+ const [, tileId, cellId] = parsed.data;
128
+ if (tileSpaceId(tileId, cellId) !== value) return null;
129
+ return { tileId, cellId };
130
+ }
131
+
132
+ // src/shared/runtime-json.ts
133
+ import * as z4 from "zod";
134
+ import { en } from "zod/locales";
135
+ z4.config(en());
136
+ var RuntimeJsonSchema = z4.json();
137
+
138
+ // src/shared/protocol/json.ts
139
+ import { sha256 } from "@noble/hashes/sha2.js";
140
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
141
+ function canonicalizePluginRuntimeJson(value) {
142
+ return canonicalizeJson(
143
+ RuntimeJsonSchema.parse(omitOptionalProperties(value))
144
+ );
145
+ }
146
+ function encodeCanonicalPluginRuntimeJson(value) {
147
+ return JSON.stringify(canonicalizePluginRuntimeJson(value));
148
+ }
149
+ function digestPluginRuntimeJson(value) {
150
+ return `sha256:${sha256Hex(encodeCanonicalPluginRuntimeJson(value))}`;
151
+ }
152
+ function canonicalizeJson(value) {
153
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
154
+ return value;
155
+ }
156
+ if (typeof value === "number") return value;
157
+ if (Array.isArray(value)) {
158
+ return value.map((item) => canonicalizeJson(item));
159
+ }
160
+ return Object.fromEntries(
161
+ Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, item]) => [key, canonicalizeJson(item)])
162
+ );
163
+ }
164
+ function omitOptionalProperties(value) {
165
+ if (Array.isArray(value)) return value.map(omitOptionalProperties);
166
+ if (value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
167
+ return Object.fromEntries(
168
+ Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [key, omitOptionalProperties(item)])
169
+ );
170
+ }
171
+ return value;
172
+ }
173
+ function sha256Hex(input) {
174
+ return bytesToHex(sha256(utf8ToBytes(input)));
175
+ }
176
+
177
+ // src/shared/position-target.ts
178
+ import * as z5 from "zod";
179
+ var PositionTargetSchema = z5.strictObject({
180
+ zoneId: z5.string(),
181
+ hostId: z5.string(),
182
+ index: z5.number().int().nonnegative()
183
+ });
184
+ function isPositionTarget(value) {
185
+ return typeof value === "object" && value !== null && Object.keys(value).length === 3 && "zoneId" in value && typeof value.zoneId === "string" && "hostId" in value && typeof value.hostId === "string" && "index" in value && Number.isSafeInteger(value.index) && value.index >= 0;
186
+ }
187
+ function zonePositions(zones) {
188
+ return zones.flatMap(
189
+ ({ zoneId, hostId, size }) => Array.from({ length: size + 1 }, (_, index) => ({ zoneId, hostId, index }))
190
+ );
191
+ }
192
+
193
+ // src/shared/immutable.ts
194
+ function immutableCopy(value) {
195
+ return freeze(structuredClone(value));
196
+ }
197
+ function freeze(value) {
198
+ if (value !== null && typeof value === "object") {
199
+ for (const child of Object.values(value)) freeze(child);
200
+ Object.freeze(value);
201
+ }
202
+ return value;
203
+ }
204
+
205
+ // src/shared/domain/board-coordinates.ts
206
+ var MAXIMUM_BOARD_COORDINATE = Math.floor(Number.MAX_SAFE_INTEGER / 4);
207
+
208
+ // src/shared/hex-board.ts
209
+ import {
210
+ defineHex,
211
+ Grid,
212
+ Orientation,
213
+ ring as ringTraversal,
214
+ line as lineTraversal
215
+ } from "honeycomb-grid";
216
+ var maximumCoordinate = MAXIMUM_BOARD_COORDINATE;
217
+ var coordinateKey = ({ q, r }) => `${q},${r}`;
218
+ var directions = [
219
+ { q: 1, r: 0 },
220
+ { q: 0, r: 1 },
221
+ { q: -1, r: 1 },
222
+ { q: -1, r: 0 },
223
+ { q: 0, r: -1 },
224
+ { q: 1, r: -1 }
225
+ ];
226
+ var add = (a, b) => ({
227
+ q: a.q + b.q,
228
+ r: a.r + b.r
229
+ });
230
+ function edgeKey(hex, side) {
231
+ return side < 3 ? `${coordinateKey(hex)}:e${side}` : edgeKey(add(hex, directions[side]), side - 3);
232
+ }
233
+ function vertexCoordinates(hex, corner) {
234
+ return [
235
+ hex,
236
+ add(hex, directions[corner]),
237
+ add(hex, directions[(corner + 1) % 6])
238
+ ];
239
+ }
240
+ function vertexKey(hex, corner) {
241
+ const hexes = vertexCoordinates(hex, corner);
242
+ for (const base of hexes) {
243
+ for (let shape = 0; shape < 2; shape++) {
244
+ if ([directions[shape], directions[shape + 1]].every(
245
+ (offset) => hexes.some(
246
+ (hex2) => coordinateKey(hex2) === coordinateKey(add(base, offset))
247
+ )
248
+ ))
249
+ return `${coordinateKey(base)}:v${shape}`;
250
+ }
251
+ }
252
+ throw new Error("Invalid hex vertex.");
253
+ }
254
+ var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
255
+ function hexClass(orientation, dimensions = 1) {
256
+ return defineHex({
257
+ dimensions,
258
+ orientation: orientation === "flat" ? Orientation.FLAT : Orientation.POINTY,
259
+ origin: { x: 0, y: 0 }
260
+ });
261
+ }
262
+ function createHexTopology(board2) {
263
+ const boardId = board2.id;
264
+ const spaces = board2.spaces.map(({ id, q, r }) => ({ id, q, r })).sort((a, b) => compare(a.id, b.id));
265
+ for (const space of spaces) {
266
+ if (!Number.isSafeInteger(space.q) || !Number.isSafeInteger(space.r))
267
+ throw new Error("Hex coordinates must be safe integers.");
268
+ if (Math.abs(space.q) > maximumCoordinate || Math.abs(space.r) > maximumCoordinate)
269
+ throw new Error(
270
+ `Hex coordinates must be within +/-${maximumCoordinate} for exact cube arithmetic and safe one-step neighbours.`
271
+ );
272
+ }
273
+ const orientation = board2.orientation ?? "pointy";
274
+ const grid = new Grid(hexClass(orientation), spaces);
275
+ const spacesById = new Map(spaces.map((space) => [space.id, space]));
276
+ if (spacesById.size !== spaces.length || grid.size !== spaces.length)
277
+ throw new Error(
278
+ `Hex board '${boardId}' contains duplicate space IDs or coordinates.`
279
+ );
280
+ const spacesByCoordinate = new Map(
281
+ spaces.map((space) => [coordinateKey(space), space])
282
+ );
283
+ const cornersBySpace = /* @__PURE__ */ new Map();
284
+ const edgesBySpace = /* @__PURE__ */ new Map();
285
+ const edgesById = /* @__PURE__ */ new Map();
286
+ const verticesById = /* @__PURE__ */ new Map();
287
+ const vertexPoints = /* @__PURE__ */ new Map();
288
+ const Tile = hexClass(orientation);
289
+ const cornerId = (space, corner) => boardVertexId("hex", boardId, vertexKey(space, corner));
290
+ for (const space of spaces) {
291
+ const cornerIds = Array.from(
292
+ { length: 6 },
293
+ (_, corner) => cornerId(space, corner)
294
+ );
295
+ cornersBySpace.set(space.id, cornerIds);
296
+ const edgeIds = Array.from({ length: 6 }, (_, side) => {
297
+ const id = boardEdgeId("hex", boardId, edgeKey(space, side));
298
+ let edge = edgesById.get(id);
299
+ if (!edge) {
300
+ edge = {
301
+ id,
302
+ spaceIds: [],
303
+ vertexIds: [cornerIds[(side + 5) % 6], cornerIds[side]].sort(
304
+ compare
305
+ )
306
+ };
307
+ edgesById.set(id, edge);
308
+ }
309
+ edge.spaceIds.push(space.id);
310
+ return id;
311
+ });
312
+ edgesBySpace.set(space.id, edgeIds);
313
+ cornerIds.forEach((id, corner) => {
314
+ let vertex = verticesById.get(id);
315
+ if (!vertex) {
316
+ vertex = { id, spaceIds: [], edgeIds: [] };
317
+ verticesById.set(id, vertex);
318
+ const centers = vertexCoordinates(space, corner).map(
319
+ (coordinate7) => new Tile(coordinate7)
320
+ );
321
+ vertexPoints.set(id, {
322
+ x: centers.reduce((sum, hex) => sum + hex.x, 0) / 3,
323
+ y: centers.reduce((sum, hex) => sum + hex.y, 0) / 3
324
+ });
325
+ }
326
+ vertex.spaceIds.push(space.id);
327
+ for (const edgeId2 of [edgeIds[corner], edgeIds[(corner + 1) % 6]])
328
+ if (!vertex.edgeIds.includes(edgeId2)) vertex.edgeIds.push(edgeId2);
329
+ });
330
+ }
331
+ const edges = Object.freeze(
332
+ [...edgesById.values()].sort((a, b) => compare(a.id, b.id)).map(
333
+ (edge) => Object.freeze({
334
+ ...edge,
335
+ spaceIds: Object.freeze(edge.spaceIds),
336
+ vertexIds: Object.freeze(edge.vertexIds)
337
+ })
338
+ )
339
+ );
340
+ const vertices = Object.freeze(
341
+ [...verticesById.values()].sort((a, b) => compare(a.id, b.id)).map(
342
+ (vertex) => Object.freeze({
343
+ ...vertex,
344
+ spaceIds: Object.freeze(vertex.spaceIds),
345
+ edgeIds: Object.freeze(vertex.edgeIds.sort(compare))
346
+ })
347
+ )
348
+ );
349
+ const requireSpace = (id) => {
350
+ const space = spacesById.get(id);
351
+ if (!space) throw new Error(`Unknown space '${id}' on board '${boardId}'.`);
352
+ return space;
353
+ };
354
+ const selectedIds = (coordinates) => Array.from(coordinates).flatMap((coordinate7) => {
355
+ const space = spacesByCoordinate.get(coordinateKey(coordinate7));
356
+ return space ? [space.id] : [];
357
+ });
358
+ const topology = {
359
+ edges,
360
+ vertices,
361
+ neighbors(id) {
362
+ const space = requireSpace(id);
363
+ return directions.flatMap((direction) => {
364
+ const neighbor = spacesByCoordinate.get(
365
+ coordinateKey(add(space, direction))
366
+ );
367
+ return neighbor ? [neighbor.id] : [];
368
+ });
369
+ },
370
+ distance(from, to) {
371
+ requireSpace(from);
372
+ requireSpace(to);
373
+ const queue = [from];
374
+ const distances = /* @__PURE__ */ new Map([[from, 0]]);
375
+ for (let index = 0; index < queue.length; index++) {
376
+ const id = queue[index];
377
+ const distance = distances.get(id);
378
+ if (id === to) return distance;
379
+ const space = requireSpace(id);
380
+ for (const direction of directions) {
381
+ const neighbor = spacesByCoordinate.get(
382
+ coordinateKey(add(space, direction))
383
+ );
384
+ if (neighbor && !distances.has(neighbor.id)) {
385
+ distances.set(neighbor.id, distance + 1);
386
+ queue.push(neighbor.id);
387
+ }
388
+ }
389
+ }
390
+ return Infinity;
391
+ },
392
+ gridDistance(from, to) {
393
+ return grid.distance(requireSpace(from), requireSpace(to));
394
+ },
395
+ ring(id, radius) {
396
+ return selectedIds(
397
+ new Grid(
398
+ hexClass(orientation),
399
+ ringTraversal({ center: requireSpace(id), radius })
400
+ )
401
+ );
402
+ },
403
+ line(from, to) {
404
+ return selectedIds(
405
+ new Grid(
406
+ hexClass(orientation),
407
+ lineTraversal({ start: requireSpace(from), stop: requireSpace(to) })
408
+ )
409
+ );
410
+ },
411
+ edgesOf(id) {
412
+ requireSpace(id);
413
+ return [...edgesBySpace.get(id)];
414
+ },
415
+ verticesOf(id) {
416
+ requireSpace(id);
417
+ return [...cornersBySpace.get(id)];
418
+ },
419
+ edgeAt(id, side) {
420
+ requireSpace(id);
421
+ const key = edgesBySpace.get(id)[side];
422
+ if (!key)
423
+ throw new Error("Hex side must be an integer from 0 through 5.");
424
+ return key;
425
+ },
426
+ vertexAt(id, corner) {
427
+ requireSpace(id);
428
+ const key = cornersBySpace.get(id)[corner];
429
+ if (!key)
430
+ throw new Error("Hex corner must be an integer from 0 through 5.");
431
+ return key;
432
+ },
433
+ edge(a, b) {
434
+ requireSpace(a);
435
+ requireSpace(b);
436
+ const edge = edgesBySpace.get(a).map((id) => edgesById.get(id)).find((edge2) => edge2.spaceIds.includes(b));
437
+ if (!edge || a === b)
438
+ throw new Error(`Spaces '${a}' and '${b}' do not share an edge.`);
439
+ return edge.id;
440
+ },
441
+ vertex(a, b, c) {
442
+ requireSpace(a);
443
+ requireSpace(b);
444
+ requireSpace(c);
445
+ const vertex = cornersBySpace.get(a).map((id) => verticesById.get(id)).find(
446
+ (vertex2) => vertex2.spaceIds.includes(b) && vertex2.spaceIds.includes(c)
447
+ );
448
+ if (!vertex || (/* @__PURE__ */ new Set([a, b, c])).size !== 3)
449
+ throw new Error("Spaces do not share exactly one vertex.");
450
+ return vertex.id;
451
+ },
452
+ incidentEdges(vertexId2) {
453
+ const vertex = verticesById.get(vertexId2);
454
+ if (!vertex) throw new Error(`Unknown vertex '${vertexId2}'.`);
455
+ return [...vertex.edgeIds];
456
+ },
457
+ incidentVertices(edgeId2) {
458
+ const edge = edgesById.get(edgeId2);
459
+ if (!edge) throw new Error(`Unknown edge '${edgeId2}'.`);
460
+ return [...edge.vertexIds];
461
+ },
462
+ spacesAt(vertexId2) {
463
+ const vertex = verticesById.get(vertexId2);
464
+ if (!vertex) throw new Error(`Unknown vertex '${vertexId2}'.`);
465
+ return [...vertex.spaceIds];
466
+ },
467
+ spacesAlong(edgeId2) {
468
+ const edge = edgesById.get(edgeId2);
469
+ if (!edge) throw new Error(`Unknown edge '${edgeId2}'.`);
470
+ return [...edge.spaceIds];
471
+ },
472
+ getLayout({
473
+ hexSize,
474
+ origin: suppliedOrigin = { x: 0, y: 0 }
475
+ }) {
476
+ const origin = { x: suppliedOrigin.x, y: suppliedOrigin.y };
477
+ if (!Number.isFinite(hexSize) || hexSize <= 0)
478
+ throw new Error("hexSize must be positive and finite.");
479
+ if (![origin.x, origin.y].every(Number.isFinite))
480
+ throw new Error("Layout origin must be finite.");
481
+ const scaled = new Grid(hexClass(orientation, hexSize), spaces);
482
+ const layoutSpaces = spaces.map((space) => {
483
+ const hex = scaled.getHex(space);
484
+ return {
485
+ id: space.id,
486
+ center: { x: hex.x + origin.x, y: hex.y + origin.y },
487
+ corners: hex.corners.map((point) => ({
488
+ x: point.x + origin.x,
489
+ y: point.y + origin.y
490
+ }))
491
+ };
492
+ });
493
+ const points = layoutSpaces.flatMap((space) => space.corners);
494
+ const minX = points.length ? Math.min(...points.map((point) => point.x)) : 0;
495
+ const minY = points.length ? Math.min(...points.map((point) => point.y)) : 0;
496
+ const maxX = points.length ? Math.max(...points.map((point) => point.x)) : 0;
497
+ const maxY = points.length ? Math.max(...points.map((point) => point.y)) : 0;
498
+ const scaledVertexPoints = new Map(
499
+ [...vertexPoints].map(([id, point]) => [
500
+ id,
501
+ { x: point.x * hexSize + origin.x, y: point.y * hexSize + origin.y }
502
+ ])
503
+ );
504
+ return {
505
+ viewBox: Object.freeze({
506
+ x: minX,
507
+ y: minY,
508
+ width: maxX - minX,
509
+ height: maxY - minY
510
+ }),
511
+ spaces: Object.freeze(
512
+ layoutSpaces.map(
513
+ (space) => Object.freeze({
514
+ ...space,
515
+ center: Object.freeze(space.center),
516
+ corners: Object.freeze(
517
+ space.corners.map((point) => Object.freeze(point))
518
+ )
519
+ })
520
+ )
521
+ ),
522
+ edges: Object.freeze(
523
+ edges.map(
524
+ (edge) => Object.freeze({
525
+ id: edge.id,
526
+ from: Object.freeze(scaledVertexPoints.get(edge.vertexIds[0])),
527
+ to: Object.freeze(scaledVertexPoints.get(edge.vertexIds[1]))
528
+ })
529
+ )
530
+ ),
531
+ vertices: Object.freeze(
532
+ vertices.map(
533
+ (vertex) => Object.freeze({
534
+ id: vertex.id,
535
+ center: Object.freeze(scaledVertexPoints.get(vertex.id))
536
+ })
537
+ )
538
+ ),
539
+ pointToSpace(point) {
540
+ const hex = scaled.pointToHex(
541
+ { x: point.x - origin.x, y: point.y - origin.y },
542
+ { allowOutside: false }
543
+ );
544
+ return hex ? spacesByCoordinate.get(coordinateKey(hex))?.id : void 0;
545
+ }
546
+ };
547
+ }
548
+ };
549
+ let cachedLayout;
550
+ const buildLayout = topology.getLayout;
551
+ topology.getLayout = (options) => {
552
+ const key = JSON.stringify([
553
+ options.hexSize,
554
+ options.origin?.x ?? 0,
555
+ options.origin?.y ?? 0
556
+ ]);
557
+ if (cachedLayout?.key !== key) {
558
+ const value = buildLayout(options);
559
+ cachedLayout = { key, value: Object.freeze(value) };
560
+ }
561
+ return cachedLayout.value;
562
+ };
563
+ return Object.freeze(topology);
564
+ }
565
+ function createHexTopologyCache() {
566
+ const entries = /* @__PURE__ */ new Map();
567
+ return (board2) => {
568
+ const orientation = board2.orientation ?? "pointy";
569
+ const cached = entries.get(board2.id);
570
+ if (cached?.orientation === orientation && cached.spaces.size === board2.spaces.length) {
571
+ const seen = /* @__PURE__ */ new Set();
572
+ const equal = board2.spaces.every((space) => {
573
+ const previous = cached.spaces.get(space.id);
574
+ if (seen.has(space.id)) return false;
575
+ seen.add(space.id);
576
+ return previous?.q === space.q && previous?.r === space.r;
577
+ });
578
+ if (equal) return cached.value;
579
+ }
580
+ const value = createHexTopology(board2);
581
+ if (!entries.has(board2.id) && entries.size >= 64)
582
+ entries.delete(entries.keys().next().value);
583
+ entries.set(board2.id, {
584
+ orientation,
585
+ spaces: new Map(board2.spaces.map(({ id, q, r }) => [id, { q, r }])),
586
+ value
587
+ });
588
+ return value;
589
+ };
590
+ }
591
+
592
+ // src/shared/board-topology.ts
593
+ var BoardTopologyError = class extends Error {
594
+ name = "BoardTopologyError";
595
+ };
596
+ function coordinate(value) {
597
+ if (!Number.isSafeInteger(value) || Math.abs(value) > MAXIMUM_BOARD_COORDINATE)
598
+ throw new BoardTopologyError(
599
+ `Board coordinate must be an integer within +/-${MAXIMUM_BOARD_COORDINATE}.`
600
+ );
601
+ return value === 0 ? 0 : value;
602
+ }
603
+ function rotateHex(q, r, rotation) {
604
+ coordinate(q);
605
+ coordinate(r);
606
+ if (!Number.isInteger(rotation) || rotation < 0 || rotation > 5)
607
+ throw new BoardTopologyError(
608
+ "Hex rotation must be an integer from 0 through 5."
609
+ );
610
+ for (let step = 0; step < rotation; step++) [q, r] = [-r, q + r];
611
+ return { q: coordinate(q), r: coordinate(r) };
612
+ }
613
+ function rotateSquare(col, row, rotation) {
614
+ coordinate(col);
615
+ coordinate(row);
616
+ if (!Number.isInteger(rotation) || rotation < 0 || rotation > 3)
617
+ throw new BoardTopologyError(
618
+ "Square rotation must be an integer from 0 through 3."
619
+ );
620
+ for (let step = 0; step < rotation; step++) [col, row] = [-row, col];
621
+ return { col: coordinate(col), row: coordinate(row) };
622
+ }
623
+ function annotate(element, annotation) {
624
+ const merge = (key) => {
625
+ const before = element[key];
626
+ const after = annotation[key];
627
+ if (before !== void 0 && after !== void 0 && before !== after)
628
+ throw new BoardTopologyError(
629
+ `Conflicting ${key} metadata on '${element.id}'.`
630
+ );
631
+ return after ?? before;
632
+ };
633
+ const fields3 = { ...element.fields };
634
+ for (const [key, value] of Object.entries(annotation.fields)) {
635
+ if (Object.hasOwn(fields3, key) && encodeCanonicalPluginRuntimeJson(fields3[key]) !== encodeCanonicalPluginRuntimeJson(value))
636
+ throw new BoardTopologyError(
637
+ `Conflicting field '${key}' metadata on '${element.id}'.`
638
+ );
639
+ Object.defineProperty(fields3, key, {
640
+ value,
641
+ enumerable: true,
642
+ writable: true,
643
+ configurable: true
644
+ });
645
+ }
646
+ return { ...element, typeId: merge("typeId"), label: merge("label"), fields: fields3 };
647
+ }
648
+ function deriveBoardTopology(table, definitions, boardId) {
649
+ if (!Object.hasOwn(table.boards, boardId))
650
+ throw new BoardTopologyError(`Unknown board '${boardId}'.`);
651
+ const instance = table.boards[boardId];
652
+ if (!Object.hasOwn(definitions.boardDefinitions, instance.baseId))
653
+ throw new BoardTopologyError(
654
+ `Unknown board definition '${instance.baseId}'.`
655
+ );
656
+ const definition = definitions.boardDefinitions[instance.baseId];
657
+ const playerId = definition.scope === "perPlayer" ? parsePerPlayerInstanceId(boardId)?.playerId : void 0;
658
+ const base = {
659
+ id: boardId,
660
+ baseId: instance.baseId,
661
+ name: definition.name,
662
+ scope: definition.scope,
663
+ ...playerId === void 0 ? {} : { playerId },
664
+ typeId: definition.typeId,
665
+ fields: definition.fields,
666
+ relations: instance.relations
667
+ };
668
+ if (definition.layout === "generic") {
669
+ for (const tileId of Object.keys(table.tiles)) {
670
+ const location = table.componentLocations[tileId];
671
+ if (location?.type === "OnBoard" && "boardId" in location && location.boardId === boardId)
672
+ throw new BoardTopologyError(
673
+ `Tile '${tileId}' cannot be placed on generic board '${boardId}'.`
674
+ );
675
+ }
676
+ const result2 = {
677
+ ...base,
678
+ layout: "generic",
679
+ spaces: definition.spaces
680
+ };
681
+ validateRelations(result2);
682
+ return immutableCopy(result2);
683
+ }
684
+ const hexSpaces = {};
685
+ const squareSpaces = {};
686
+ const occupied = /* @__PURE__ */ new Map();
687
+ const placed = [];
688
+ for (const [tileId, tile] of Object.entries(table.tiles).sort(
689
+ ([a], [b]) => a < b ? -1 : a > b ? 1 : 0
690
+ )) {
691
+ const location = table.componentLocations[tileId];
692
+ if (location?.type !== "OnBoard" || !("boardId" in location) || location.boardId !== boardId)
693
+ continue;
694
+ if (!Object.hasOwn(definitions.tileDefinitions, tile.tileTypeId))
695
+ throw new BoardTopologyError(`Unknown tile type '${tile.tileTypeId}'.`);
696
+ const tileDefinition = definitions.tileDefinitions[tile.tileTypeId];
697
+ if (!("layout" in location) || location.layout !== definition.layout || tileDefinition.layout !== definition.layout)
698
+ throw new BoardTopologyError(
699
+ `Tile '${tileId}' layout does not match board '${boardId}'.`
700
+ );
701
+ const placement2 = location;
702
+ placed.push({ tileId, definition: tileDefinition, placement: placement2 });
703
+ if (tileDefinition.layout === "hex" && placement2.layout === "hex") {
704
+ coordinate(placement2.q);
705
+ coordinate(placement2.r);
706
+ for (const cell2 of tileDefinition.cells) {
707
+ const rotated = rotateHex(cell2.at.q, cell2.at.r, placement2.rotation);
708
+ const q = coordinate(rotated.q + placement2.q), r = coordinate(rotated.r + placement2.r);
709
+ const key = `${q},${r}`;
710
+ if (occupied.has(key))
711
+ throw new BoardTopologyError(
712
+ `Overlapping tile cells from tiles '${occupied.get(key)}' and '${tileId}' on board '${boardId}' at ${key}.`
713
+ );
714
+ occupied.set(key, tileId);
715
+ const id = tileSpaceId(tileId, cell2.id);
716
+ hexSpaces[id] = {
717
+ id,
718
+ tileId,
719
+ localCellId: cell2.id,
720
+ q,
721
+ r,
722
+ name: cell2.name,
723
+ typeId: cell2.typeId,
724
+ fields: cell2.fields
725
+ };
726
+ }
727
+ } else if (tileDefinition.layout === "square" && placement2.layout === "square") {
728
+ coordinate(placement2.col);
729
+ coordinate(placement2.row);
730
+ for (const cell2 of tileDefinition.cells) {
731
+ const rotated = rotateSquare(
732
+ cell2.at.col,
733
+ cell2.at.row,
734
+ placement2.rotation
735
+ );
736
+ const col = coordinate(rotated.col + placement2.col), row = coordinate(rotated.row + placement2.row);
737
+ const key = `${col},${row}`;
738
+ if (occupied.has(key))
739
+ throw new BoardTopologyError(
740
+ `Overlapping tile cells from tiles '${occupied.get(key)}' and '${tileId}' on board '${boardId}' at ${key}.`
741
+ );
742
+ occupied.set(key, tileId);
743
+ const id = tileSpaceId(tileId, cell2.id);
744
+ squareSpaces[id] = {
745
+ id,
746
+ tileId,
747
+ localCellId: cell2.id,
748
+ col,
749
+ row,
750
+ name: cell2.name,
751
+ typeId: cell2.typeId,
752
+ fields: cell2.fields
753
+ };
754
+ }
755
+ }
756
+ }
757
+ if (definition.layout === "hex") {
758
+ const lattice2 = createHexTopology({
759
+ id: boardId,
760
+ orientation: definition.orientation,
761
+ spaces: Object.values(hexSpaces)
762
+ });
763
+ const edges = new Map(
764
+ lattice2.edges.map((edge) => [edge.id, { ...edge, fields: {} }])
765
+ );
766
+ const vertices = new Map(
767
+ lattice2.vertices.map((vertex) => [vertex.id, { ...vertex, fields: {} }])
768
+ );
769
+ for (const tile of placed) {
770
+ if (tile.definition.layout !== "hex" || tile.placement.layout !== "hex")
771
+ continue;
772
+ for (const annotation of tile.definition.edges) {
773
+ const id = lattice2.edgeAt(
774
+ tileSpaceId(tile.tileId, annotation.cellId),
775
+ hexSide(annotation.side + tile.placement.rotation)
776
+ );
777
+ edges.set(id, annotate(edges.get(id), annotation));
778
+ }
779
+ for (const annotation of tile.definition.vertices) {
780
+ const id = lattice2.vertexAt(
781
+ tileSpaceId(tile.tileId, annotation.cellId),
782
+ hexSide(annotation.corner + tile.placement.rotation)
783
+ );
784
+ vertices.set(id, annotate(vertices.get(id), annotation));
785
+ }
786
+ }
787
+ const result2 = {
788
+ ...base,
789
+ layout: "hex",
790
+ orientation: definition.orientation,
791
+ spaces: hexSpaces,
792
+ edges: [...edges.values()].sort(
793
+ (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0
794
+ ),
795
+ vertices: [...vertices.values()].sort(
796
+ (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0
797
+ )
798
+ };
799
+ validateRelations(result2);
800
+ return immutableCopy(result2);
801
+ }
802
+ const lattice = squareLattice(boardId, Object.values(squareSpaces));
803
+ for (const tile of placed) {
804
+ if (tile.definition.layout !== "square" || tile.placement.layout !== "square")
805
+ continue;
806
+ for (const annotation of tile.definition.edges) {
807
+ const id = lattice.edgeAt(
808
+ squareSpaces[tileSpaceId(tile.tileId, annotation.cellId)],
809
+ (annotation.side + tile.placement.rotation) % 4
810
+ );
811
+ lattice.edges.set(id, annotate(lattice.edges.get(id), annotation));
812
+ }
813
+ for (const annotation of tile.definition.vertices) {
814
+ const id = lattice.vertexAt(
815
+ squareSpaces[tileSpaceId(tile.tileId, annotation.cellId)],
816
+ (annotation.corner + tile.placement.rotation) % 4
817
+ );
818
+ lattice.vertices.set(id, annotate(lattice.vertices.get(id), annotation));
819
+ }
820
+ }
821
+ const result = {
822
+ ...base,
823
+ layout: "square",
824
+ spaces: squareSpaces,
825
+ edges: [...lattice.edges.values()].sort(
826
+ (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0
827
+ ),
828
+ vertices: [...lattice.vertices.values()].sort(
829
+ (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0
830
+ )
831
+ };
832
+ validateRelations(result);
833
+ return immutableCopy(result);
834
+ }
835
+ function hexSide(value) {
836
+ const side = value % 6;
837
+ switch (side) {
838
+ case 0:
839
+ case 1:
840
+ case 2:
841
+ case 3:
842
+ case 4:
843
+ case 5:
844
+ return side;
845
+ default:
846
+ throw new BoardTopologyError("Invalid hex side.");
847
+ }
848
+ }
849
+ function validateRelations(board2) {
850
+ const identities = /* @__PURE__ */ new Set();
851
+ for (const relation of board2.relations) {
852
+ if (typeof relation.id !== "string" || relation.id.length === 0)
853
+ throw new BoardTopologyError(
854
+ `Relation identity must be nonempty on board '${board2.id}'.`
855
+ );
856
+ if (identities.has(relation.id))
857
+ throw new BoardTopologyError(
858
+ `Duplicate relation identity '${relation.id}' on board '${board2.id}'.`
859
+ );
860
+ identities.add(relation.id);
861
+ if (!Object.hasOwn(board2.spaces, relation.fromSpaceId) || !Object.hasOwn(board2.spaces, relation.toSpaceId))
862
+ throw new BoardTopologyError(
863
+ `Relation on board '${board2.id}' references an absent space.`
864
+ );
865
+ }
866
+ }
867
+ function squareLattice(boardId, spaces) {
868
+ const edges = /* @__PURE__ */ new Map();
869
+ const vertices = /* @__PURE__ */ new Map();
870
+ const corners = [
871
+ [1, 1],
872
+ [0, 1],
873
+ [0, 0],
874
+ [1, 0]
875
+ ];
876
+ const vertexAt = (space, corner) => {
877
+ const offset = corners[corner];
878
+ if (!space || !offset)
879
+ throw new BoardTopologyError("Unknown square cell or corner.");
880
+ return boardVertexId(
881
+ "square",
882
+ boardId,
883
+ `${space.col + offset[0]},${space.row + offset[1]}`
884
+ );
885
+ };
886
+ const edgeAt = (space, side) => {
887
+ if (!space || !Number.isInteger(side) || side < 0 || side > 3)
888
+ throw new BoardTopologyError("Unknown square cell or side.");
889
+ switch (side) {
890
+ case 0:
891
+ return boardEdgeId(
892
+ "square",
893
+ boardId,
894
+ `${space.col + 1},${space.row}:v`
895
+ );
896
+ case 1:
897
+ return boardEdgeId(
898
+ "square",
899
+ boardId,
900
+ `${space.col},${space.row + 1}:h`
901
+ );
902
+ case 2:
903
+ return boardEdgeId("square", boardId, `${space.col},${space.row}:v`);
904
+ default:
905
+ return boardEdgeId("square", boardId, `${space.col},${space.row}:h`);
906
+ }
907
+ };
908
+ for (const space of spaces) {
909
+ for (let side = 0; side < 4; side++) {
910
+ const id = edgeAt(space, side);
911
+ const vertexIds = [vertexAt(space, (side + 3) % 4), vertexAt(space, side)];
912
+ const before = edges.get(id);
913
+ edges.set(id, {
914
+ id,
915
+ spaceIds: [...before?.spaceIds ?? [], space.id].sort(),
916
+ vertexIds: vertexIds.sort(),
917
+ fields: {}
918
+ });
919
+ for (const vertexId2 of vertexIds) {
920
+ const vertex = vertices.get(vertexId2);
921
+ vertices.set(vertexId2, {
922
+ id: vertexId2,
923
+ spaceIds: [
924
+ .../* @__PURE__ */ new Set([...vertex?.spaceIds ?? [], space.id])
925
+ ].sort(),
926
+ edgeIds: [.../* @__PURE__ */ new Set([...vertex?.edgeIds ?? [], id])].sort(),
927
+ fields: {}
928
+ });
929
+ }
930
+ }
931
+ }
932
+ return { edges, vertices, edgeAt, vertexAt };
933
+ }
934
+ function createBoardTopologyCache() {
935
+ const entries = /* @__PURE__ */ new Map();
936
+ return (table, definitions, boardId) => {
937
+ if (!Object.hasOwn(table.boards, boardId))
938
+ throw new BoardTopologyError(`Unknown board '${boardId}'.`);
939
+ const instance = table.boards[boardId];
940
+ const placements = Object.entries(table.tiles).flatMap(([id, tile]) => {
941
+ const location = table.componentLocations[id];
942
+ return location?.type === "OnBoard" && "boardId" in location && location.boardId === boardId ? [
943
+ {
944
+ id,
945
+ tileTypeId: tile.tileTypeId,
946
+ location,
947
+ definition: definitions.tileDefinitions[tile.tileTypeId]
948
+ }
949
+ ] : [];
950
+ }).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
951
+ const key = encodeCanonicalPluginRuntimeJson({
952
+ instance,
953
+ definition: definitions.boardDefinitions[instance.baseId],
954
+ placements
955
+ });
956
+ const previous = entries.get(boardId);
957
+ if (previous?.key === key) return previous.topology;
958
+ const topology = deriveBoardTopology(table, definitions, boardId);
959
+ if (!entries.has(boardId) && entries.size >= 64)
960
+ entries.delete(entries.keys().next().value);
961
+ entries.set(boardId, { key, topology });
962
+ return topology;
963
+ };
964
+ }
965
+
966
+ // src/shared/board-topology-schema.ts
967
+ import * as z6 from "zod";
968
+ var coordinate2 = z6.number().int().min(-MAXIMUM_BOARD_COORDINATE).max(MAXIMUM_BOARD_COORDINATE);
969
+ var fields = z6.record(z6.string(), RuntimeJsonSchema);
970
+ var BoardRelationSchema = z6.strictObject({
971
+ id: z6.string().min(1),
972
+ typeId: z6.string().min(1),
973
+ fromSpaceId: z6.string().min(1),
974
+ toSpaceId: z6.string().min(1),
975
+ directed: z6.boolean(),
976
+ fields
977
+ });
978
+ var BoardSpaceSchema = z6.strictObject({
979
+ id: z6.string().min(1),
980
+ name: z6.string().nullable().optional(),
981
+ typeId: z6.string().min(1).nullable().optional(),
982
+ fields
983
+ });
984
+ var tileCell = BoardSpaceSchema.extend({
985
+ id: z6.custom((value) => parseTileSpaceId(value) !== null),
986
+ tileId: z6.string().min(1),
987
+ localCellId: z6.string().min(1)
988
+ });
989
+ function checkTileCell(cell2, ctx) {
990
+ const identity = parseTileSpaceId(cell2.id);
991
+ if (identity?.tileId !== cell2.tileId || identity?.cellId !== cell2.localCellId)
992
+ ctx.addIssue({
993
+ code: "custom",
994
+ path: ["id"],
995
+ message: "Tile space identity must match tileId and localCellId."
996
+ });
997
+ }
998
+ var HexSpaceSchema = tileCell.extend({ q: coordinate2, r: coordinate2 }).superRefine(checkTileCell);
999
+ var SquareSpaceSchema = tileCell.extend({ col: coordinate2, row: coordinate2 }).superRefine(checkTileCell);
1000
+ var edgeId = z6.custom(
1001
+ (value) => parseBoardElementId(value)?.kind === "edge"
1002
+ );
1003
+ var vertexId = z6.custom(
1004
+ (value) => parseBoardElementId(value)?.kind === "vertex"
1005
+ );
1006
+ var BoardEdgeSchema = z6.strictObject({
1007
+ id: edgeId,
1008
+ spaceIds: z6.array(z6.string().min(1)).min(1),
1009
+ vertexIds: z6.tuple([vertexId, vertexId]),
1010
+ typeId: z6.string().min(1).optional(),
1011
+ label: z6.string().optional(),
1012
+ fields
1013
+ });
1014
+ var BoardVertexSchema = z6.strictObject({
1015
+ id: vertexId,
1016
+ spaceIds: z6.array(z6.string().min(1)).min(1),
1017
+ edgeIds: z6.array(edgeId).min(1),
1018
+ typeId: z6.string().min(1).optional(),
1019
+ label: z6.string().optional(),
1020
+ fields
1021
+ });
1022
+ function identityRecord(schema) {
1023
+ return z6.record(z6.string().min(1), schema).superRefine((entries, ctx) => {
1024
+ for (const [key, value] of Object.entries(entries)) {
1025
+ if (value.id !== key)
1026
+ ctx.addIssue({
1027
+ code: "custom",
1028
+ path: [key, "id"],
1029
+ message: "Record key must match item identity."
1030
+ });
1031
+ }
1032
+ });
1033
+ }
1034
+ var board = z6.strictObject({
1035
+ id: z6.string().min(1),
1036
+ baseId: z6.string().min(1),
1037
+ name: z6.string(),
1038
+ scope: z6.enum(["shared", "perPlayer"]),
1039
+ playerId: z6.string().min(1).optional(),
1040
+ typeId: z6.string().min(1).optional(),
1041
+ fields,
1042
+ relations: z6.array(BoardRelationSchema)
1043
+ });
1044
+ var GenericBoardTopologySchema = board.extend({
1045
+ layout: z6.literal("generic"),
1046
+ spaces: identityRecord(BoardSpaceSchema)
1047
+ });
1048
+ var HexBoardTopologySchema = board.extend({
1049
+ layout: z6.literal("hex"),
1050
+ orientation: z6.enum(["pointy", "flat"]),
1051
+ spaces: identityRecord(HexSpaceSchema),
1052
+ edges: z6.array(BoardEdgeSchema),
1053
+ vertices: z6.array(BoardVertexSchema)
1054
+ });
1055
+ var SquareBoardTopologySchema = board.extend({
1056
+ layout: z6.literal("square"),
1057
+ spaces: identityRecord(SquareSpaceSchema),
1058
+ edges: z6.array(BoardEdgeSchema),
1059
+ vertices: z6.array(BoardVertexSchema)
1060
+ });
1061
+ function validateTopologyGraph(topology, ctx) {
1062
+ const issue = (path, message) => ctx.addIssue({ code: "custom", path, message });
1063
+ const replica = parsePerPlayerInstanceId(topology.id);
1064
+ if (topology.scope === "perPlayer") {
1065
+ if (replica?.family !== "board" || replica.baseId !== topology.baseId || replica.playerId !== topology.playerId)
1066
+ issue(
1067
+ ["playerId"],
1068
+ "Per-player board identity must match baseId and playerId."
1069
+ );
1070
+ } else if (topology.playerId !== void 0 || replica !== null || topology.id !== topology.baseId) {
1071
+ issue(
1072
+ ["playerId"],
1073
+ "Shared board identity must match baseId and cannot carry a player identity."
1074
+ );
1075
+ }
1076
+ const spaces = new Set(Object.keys(topology.spaces));
1077
+ const relationIds = /* @__PURE__ */ new Set();
1078
+ for (const [index, relation] of topology.relations.entries()) {
1079
+ if (!spaces.has(relation.fromSpaceId) || !spaces.has(relation.toSpaceId))
1080
+ issue(
1081
+ ["relations", index],
1082
+ "Relation endpoints must belong to this topology."
1083
+ );
1084
+ if (relation.id != null) {
1085
+ if (relationIds.has(relation.id))
1086
+ issue(
1087
+ ["relations", index, "id"],
1088
+ "Relation identities must be unique."
1089
+ );
1090
+ relationIds.add(relation.id);
1091
+ }
1092
+ }
1093
+ if (topology.layout === "generic") return;
1094
+ const edges = new Map(topology.edges.map((edge) => [edge.id, edge]));
1095
+ const vertices = new Map(
1096
+ topology.vertices.map((vertex) => [vertex.id, vertex])
1097
+ );
1098
+ if (edges.size !== topology.edges.length)
1099
+ issue(["edges"], "Edge identities must be unique.");
1100
+ if (vertices.size !== topology.vertices.length)
1101
+ issue(["vertices"], "Vertex identities must be unique.");
1102
+ const references = (ids, known, path) => {
1103
+ const seen = /* @__PURE__ */ new Set();
1104
+ for (const [index, id] of ids.entries()) {
1105
+ if (seen.has(id))
1106
+ issue([...path, index], "Incidence references must be unique.");
1107
+ if (!known.has(id))
1108
+ issue(
1109
+ [...path, index],
1110
+ "Incidence reference must belong to this topology."
1111
+ );
1112
+ seen.add(id);
1113
+ }
1114
+ };
1115
+ const edgeIds = new Set(edges.keys());
1116
+ const vertexIds = new Set(vertices.keys());
1117
+ const vertexEdges = new Map(
1118
+ topology.vertices.map((vertex) => [vertex.id, new Set(vertex.edgeIds)])
1119
+ );
1120
+ for (const [index, edge] of topology.edges.entries()) {
1121
+ references(edge.spaceIds, spaces, ["edges", index, "spaceIds"]);
1122
+ references(edge.vertexIds, vertexIds, ["edges", index, "vertexIds"]);
1123
+ for (const vertexId2 of edge.vertexIds) {
1124
+ if (vertices.has(vertexId2) && !vertexEdges.get(vertexId2)?.has(edge.id))
1125
+ issue(
1126
+ ["edges", index, "vertexIds"],
1127
+ "Edge and vertex incidence must agree."
1128
+ );
1129
+ }
1130
+ }
1131
+ for (const [index, vertex] of topology.vertices.entries()) {
1132
+ references(vertex.spaceIds, spaces, ["vertices", index, "spaceIds"]);
1133
+ references(vertex.edgeIds, edgeIds, ["vertices", index, "edgeIds"]);
1134
+ for (const edgeId2 of vertex.edgeIds) {
1135
+ const edge = edges.get(edgeId2);
1136
+ if (edge && !edge.vertexIds.includes(vertex.id))
1137
+ issue(
1138
+ ["vertices", index, "edgeIds"],
1139
+ "Edge and vertex incidence must agree."
1140
+ );
1141
+ }
1142
+ }
1143
+ for (const [kind, items] of [
1144
+ ["edges", topology.edges],
1145
+ ["vertices", topology.vertices]
1146
+ ]) {
1147
+ for (const [index, item] of items.entries()) {
1148
+ const ids = [
1149
+ item.id,
1150
+ ..."vertexIds" in item ? item.vertexIds : item.edgeIds
1151
+ ];
1152
+ if (ids.some((id) => {
1153
+ const identity = parseBoardElementId(id);
1154
+ return identity?.boardId !== topology.id || identity?.layout !== topology.layout;
1155
+ }))
1156
+ ctx.addIssue({
1157
+ code: "custom",
1158
+ path: [kind, index],
1159
+ message: "World element identity must match its board and layout."
1160
+ });
1161
+ }
1162
+ }
1163
+ }
1164
+ var BoardTopologySchema = z6.discriminatedUnion("layout", [
1165
+ GenericBoardTopologySchema,
1166
+ HexBoardTopologySchema,
1167
+ SquareBoardTopologySchema
1168
+ ]).superRefine(validateTopologyGraph);
1169
+ var BoardTopologyMapSchema = identityRecord(BoardTopologySchema);
1170
+
1171
+ // src/shared/domain/player-identity.ts
1172
+ import * as z7 from "zod";
1173
+ function isPlayerIdValue(value) {
1174
+ return typeof value === "string" && value.length > 0 && value !== "__proto__";
1175
+ }
1176
+ var PlayerIdSchema = z7.string().min(1).refine((value) => isPlayerIdValue(value), {
1177
+ error: "Player id '__proto__' is reserved for record/parser interoperability."
1178
+ });
1179
+ var PlayerRosterSchema = z7.array(PlayerIdSchema).refine((ids) => new Set(ids).size === ids.length, {
1180
+ error: "Duplicate player id"
1181
+ });
1182
+
1183
+ // src/shared/domain/seat-reference.ts
1184
+ import * as z8 from "zod";
1185
+ var SeatTileRefSchema = z8.string().regex(/^tile-ref:sha256:[0-9a-f]{64}$/).brand();
1186
+ var SeatSpaceRefSchema = z8.string().regex(/^space-ref:sha256:[0-9a-f]{64}$/).brand();
1187
+
1188
+ // src/shared/tile-appearance.ts
1189
+ function coordinate3(value) {
1190
+ if (!Number.isSafeInteger(value) || Math.abs(value) > MAXIMUM_BOARD_COORDINATE)
1191
+ throw new Error(
1192
+ "Public tile footprint exceeds the board coordinate domain."
1193
+ );
1194
+ return value === 0 ? 0 : value;
1195
+ }
1196
+ function getPublicTileFootprint(appearance, placement2) {
1197
+ if (appearance.layout === "hex" && placement2.layout === "hex")
1198
+ return {
1199
+ layout: "hex",
1200
+ cells: appearance.cells.map((cell2) => {
1201
+ const rotated = rotateHex(cell2.q, cell2.r, placement2.rotation);
1202
+ return {
1203
+ q: coordinate3(rotated.q + placement2.q),
1204
+ r: coordinate3(rotated.r + placement2.r)
1205
+ };
1206
+ })
1207
+ };
1208
+ if (appearance.layout === "square" && placement2.layout === "square")
1209
+ return {
1210
+ layout: "square",
1211
+ cells: appearance.cells.map((cell2) => {
1212
+ const rotated = rotateSquare(cell2.col, cell2.row, placement2.rotation);
1213
+ return {
1214
+ col: coordinate3(rotated.col + placement2.col),
1215
+ row: coordinate3(rotated.row + placement2.row)
1216
+ };
1217
+ })
1218
+ };
1219
+ throw new Error(
1220
+ "Tile public appearance must match its board placement layout."
1221
+ );
1222
+ }
1223
+
1224
+ // src/shared/seat-topology-schema.ts
1225
+ import * as z11 from "zod";
1226
+
1227
+ // src/shared/domain/tile-disclosure.ts
1228
+ import * as z9 from "zod";
1229
+ var coordinate4 = z9.int().min(-MAXIMUM_BOARD_COORDINATE).max(MAXIMUM_BOARD_COORDINATE);
1230
+ var backImage = z9.string().regex(
1231
+ /^assets\/(?:[\w-][\w.-]*\/)*[\w-][\w.-]*\.(?:avif|gif|jpe?g|png|svg|webp)$/i,
1232
+ { error: "Tile back image must be an image path under assets/." }
1233
+ );
1234
+ var PublicTileAppearanceSchema = z9.discriminatedUnion("layout", [
1235
+ z9.strictObject({
1236
+ layout: z9.literal("hex"),
1237
+ cells: z9.array(z9.strictObject({ q: coordinate4, r: coordinate4 })).nonempty(),
1238
+ backImage: backImage.optional()
1239
+ }),
1240
+ z9.strictObject({
1241
+ layout: z9.literal("square"),
1242
+ cells: z9.array(z9.strictObject({ col: coordinate4, row: coordinate4 })).nonempty(),
1243
+ backImage: backImage.optional()
1244
+ })
1245
+ ]).superRefine((appearance, ctx) => {
1246
+ const seen = /* @__PURE__ */ new Set();
1247
+ for (const [index, cell2] of appearance.cells.entries()) {
1248
+ const key = "q" in cell2 ? `${cell2.q},${cell2.r}` : `${cell2.col},${cell2.row}`;
1249
+ if (seen.has(key))
1250
+ ctx.addIssue({
1251
+ code: "custom",
1252
+ path: ["cells", index],
1253
+ message: "Public footprint coordinates must be unique."
1254
+ });
1255
+ seen.add(key);
1256
+ }
1257
+ });
1258
+ var publicFace = z9.strictObject({ audience: z9.literal("public") });
1259
+ var ownerFace = z9.strictObject({ audience: z9.literal("owner") });
1260
+ var noFace = z9.strictObject({ audience: z9.literal("none") });
1261
+ var AuthoredTileFaceAudienceSchema = z9.discriminatedUnion("audience", [
1262
+ publicFace,
1263
+ ownerFace,
1264
+ noFace
1265
+ ]);
1266
+ var TileFaceAudienceSchema = z9.discriminatedUnion("audience", [
1267
+ publicFace,
1268
+ ownerFace,
1269
+ noFace,
1270
+ z9.strictObject({
1271
+ audience: z9.literal("seats"),
1272
+ playerIds: z9.array(PlayerIdSchema).nonempty()
1273
+ }).superRefine((face, ctx) => {
1274
+ if (new Set(face.playerIds).size !== face.playerIds.length)
1275
+ ctx.addIssue({
1276
+ code: "custom",
1277
+ path: ["playerIds"],
1278
+ message: "Face audience seats must be unique."
1279
+ });
1280
+ })
1281
+ ]);
1282
+ var AuthoredTileDisclosureSchema = z9.strictObject({
1283
+ face: AuthoredTileFaceAudienceSchema,
1284
+ appearance: PublicTileAppearanceSchema.optional()
1285
+ });
1286
+ var TileDisclosureSchema = z9.strictObject({
1287
+ face: TileFaceAudienceSchema,
1288
+ appearance: PublicTileAppearanceSchema.optional()
1289
+ });
1290
+
1291
+ // src/shared/domain/tile-placement.ts
1292
+ import * as z10 from "zod";
1293
+ var coordinate5 = z10.number().int().min(-MAXIMUM_BOARD_COORDINATE).max(MAXIMUM_BOARD_COORDINATE);
1294
+ var TilePlacementSchema = z10.discriminatedUnion("layout", [
1295
+ z10.strictObject({
1296
+ type: z10.literal("OnBoard"),
1297
+ layout: z10.literal("hex"),
1298
+ boardId: z10.string().min(1),
1299
+ q: coordinate5,
1300
+ r: coordinate5,
1301
+ rotation: z10.union([
1302
+ z10.literal(0),
1303
+ z10.literal(1),
1304
+ z10.literal(2),
1305
+ z10.literal(3),
1306
+ z10.literal(4),
1307
+ z10.literal(5)
1308
+ ])
1309
+ }),
1310
+ z10.strictObject({
1311
+ type: z10.literal("OnBoard"),
1312
+ layout: z10.literal("square"),
1313
+ boardId: z10.string().min(1),
1314
+ col: coordinate5,
1315
+ row: coordinate5,
1316
+ rotation: z10.union([z10.literal(0), z10.literal(1), z10.literal(2), z10.literal(3)])
1317
+ })
1318
+ ]);
1319
+
1320
+ // src/shared/seat-topology-schema.ts
1321
+ var coordinate6 = z11.int().min(-MAXIMUM_BOARD_COORDINATE).max(MAXIMUM_BOARD_COORDINATE);
1322
+ var fields2 = z11.record(z11.string(), RuntimeJsonSchema);
1323
+ var ProjectedTileSchema = z11.discriminatedUnion("disclosure", [
1324
+ z11.strictObject({
1325
+ disclosure: z11.literal("visible"),
1326
+ ref: SeatTileRefSchema,
1327
+ tileTypeId: z11.string().min(1),
1328
+ name: z11.string(),
1329
+ frontImage: z11.string().optional(),
1330
+ ownerId: z11.string().min(1).nullable(),
1331
+ fields: fields2,
1332
+ properties: fields2
1333
+ }),
1334
+ z11.strictObject({
1335
+ disclosure: z11.literal("concealed"),
1336
+ ref: SeatTileRefSchema,
1337
+ appearance: PublicTileAppearanceSchema
1338
+ })
1339
+ ]);
1340
+ var placement = z11.discriminatedUnion("layout", [
1341
+ TilePlacementSchema.options[0].omit({ type: true, boardId: true }),
1342
+ TilePlacementSchema.options[1].omit({ type: true, boardId: true })
1343
+ ]);
1344
+ var placedTile = z11.discriminatedUnion("disclosure", [
1345
+ ProjectedTileSchema.options[0].extend({ placement }),
1346
+ ProjectedTileSchema.options[1].extend({ placement })
1347
+ ]);
1348
+ var cell = BoardSpaceSchema.extend({
1349
+ id: SeatSpaceRefSchema,
1350
+ tileRef: SeatTileRefSchema,
1351
+ localCellId: z11.string().min(1)
1352
+ });
1353
+ var SeatHexSpaceSchema = cell.extend({ q: coordinate6, r: coordinate6 });
1354
+ var SeatSquareSpaceSchema = cell.extend({
1355
+ col: coordinate6,
1356
+ row: coordinate6
1357
+ });
1358
+ var SeatBoardTopologySchema = z11.discriminatedUnion("layout", [
1359
+ GenericBoardTopologySchema,
1360
+ HexBoardTopologySchema.extend({
1361
+ spaces: identityRecord(SeatHexSpaceSchema),
1362
+ tiles: z11.array(placedTile)
1363
+ }),
1364
+ SquareBoardTopologySchema.extend({
1365
+ spaces: identityRecord(SeatSquareSpaceSchema),
1366
+ tiles: z11.array(placedTile)
1367
+ })
1368
+ ]).superRefine((board2, ctx) => {
1369
+ validateTopologyGraph(board2, ctx);
1370
+ if (board2.layout === "generic") return;
1371
+ const tiles = new Map(board2.tiles.map((tile) => [tile.ref, tile]));
1372
+ if (tiles.size !== board2.tiles.length)
1373
+ ctx.addIssue({
1374
+ code: "custom",
1375
+ path: ["tiles"],
1376
+ message: "Tile references must be unique."
1377
+ });
1378
+ for (const [index, tile] of board2.tiles.entries()) {
1379
+ if (tile.placement.layout !== board2.layout || tile.disclosure === "concealed" && tile.appearance.layout !== board2.layout)
1380
+ ctx.addIssue({
1381
+ code: "custom",
1382
+ path: ["tiles", index],
1383
+ message: "Tile presentation must match its board layout."
1384
+ });
1385
+ }
1386
+ for (const [index, tile] of board2.tiles.entries()) {
1387
+ if (tile.disclosure !== "concealed") continue;
1388
+ try {
1389
+ getPublicTileFootprint(tile.appearance, tile.placement);
1390
+ } catch (error) {
1391
+ ctx.addIssue({
1392
+ code: "custom",
1393
+ path: ["tiles", index, "appearance"],
1394
+ message: error instanceof Error ? error.message : String(error)
1395
+ });
1396
+ }
1397
+ }
1398
+ const cellsByTile = /* @__PURE__ */ new Map();
1399
+ for (const [id, space] of Object.entries(board2.spaces)) {
1400
+ const cells = cellsByTile.get(space.tileRef) ?? /* @__PURE__ */ new Set();
1401
+ if (cells.has(space.localCellId))
1402
+ ctx.addIssue({
1403
+ code: "custom",
1404
+ path: ["spaces", id],
1405
+ message: "A visible tile cell may be projected only once."
1406
+ });
1407
+ cells.add(space.localCellId);
1408
+ cellsByTile.set(space.tileRef, cells);
1409
+ if (tiles.get(space.tileRef)?.disclosure !== "visible")
1410
+ ctx.addIssue({
1411
+ code: "custom",
1412
+ path: ["spaces", id],
1413
+ message: "A projected cell must belong to a visible tile."
1414
+ });
1415
+ }
1416
+ for (const [index, tile] of board2.tiles.entries())
1417
+ if (tile.disclosure === "visible" && !cellsByTile.get(tile.ref)?.size)
1418
+ ctx.addIssue({
1419
+ code: "custom",
1420
+ path: ["tiles", index],
1421
+ message: "A visible tile requires at least one projected cell."
1422
+ });
1423
+ });
1424
+ var BoardProjectionSchema = identityRecord(SeatBoardTopologySchema);
1425
+
1426
+ // src/shared/lookup.ts
1427
+ function requireLookup(value, kind, id) {
1428
+ if (value === void 0) throw new Error(`${kind} "${id}" is not present.`);
1429
+ return value;
1430
+ }
1431
+
1432
+ // src/shared/board-target.ts
1433
+ import * as z12 from "zod";
1434
+ var BoardSpaceTargetSchema = z12.strictObject({
1435
+ boardId: z12.string(),
1436
+ spaceId: z12.string()
1437
+ });
1438
+ function isBoardSpaceTarget(value) {
1439
+ return typeof value === "object" && value !== null && Object.keys(value).length === 2 && "boardId" in value && typeof value.boardId === "string" && "spaceId" in value && typeof value.spaceId === "string";
1440
+ }
1441
+ function sameBoardSpaceTarget(left, right) {
1442
+ return left.boardId === right.boardId && left.spaceId === right.spaceId;
1443
+ }
1444
+
1445
+ // src/shared/input-domain.ts
1446
+ function inputDomainErrors(domain, value, selection, options = {}) {
1447
+ if (selection?.mode === "many") {
1448
+ if (!Array.isArray(value)) return ["Expected a list of values."];
1449
+ const errors = cardinalityErrors(
1450
+ value,
1451
+ selection.min,
1452
+ selection.max,
1453
+ "value",
1454
+ options.ignoreMinimum
1455
+ );
1456
+ if (selection.distinct && new Set(value.map(inputValueKey)).size !== value.length)
1457
+ errors.push("Choose each value only once.");
1458
+ if (value.some(
1459
+ (item) => inputDomainErrors(domain, item, void 0, options).length > 0
1460
+ ))
1461
+ errors.push(ineligibleMessage(domain));
1462
+ return errors;
1463
+ }
1464
+ switch (domain.type) {
1465
+ case "choice":
1466
+ return inputTargetInDomain(domain, value) ? [] : [ineligibleMessage(domain)];
1467
+ case "choiceList": {
1468
+ if (!Array.isArray(value)) return ["Expected a list of choices."];
1469
+ const errors = cardinalityErrors(
1470
+ value,
1471
+ domain.min ?? 0,
1472
+ domain.max ?? domain.choices.length,
1473
+ "option",
1474
+ options.ignoreMinimum
1475
+ );
1476
+ if (value.some((item) => !inputTargetInDomain(domain, item)))
1477
+ errors.push(ineligibleMessage(domain));
1478
+ return errors;
1479
+ }
1480
+ case "tileTarget":
1481
+ case "cardTarget":
1482
+ case "boardTarget":
1483
+ case "zonePosition":
1484
+ return inputTargetInDomain(domain, value) ? [] : [ineligibleMessage(domain)];
1485
+ case "boundedNumber":
1486
+ return typeof value === "number" && value >= domain.min && value <= domain.max && Math.abs(
1487
+ (value - domain.min) / (domain.step ?? 1) - Math.round((value - domain.min) / (domain.step ?? 1))
1488
+ ) < 1e-9 ? [] : ["Value is outside the current input domain."];
1489
+ case "resourceMap":
1490
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).every(
1491
+ (key) => domain.resources.some((entry) => entry.resourceId === key)
1492
+ ) && domain.resources.every((entry) => {
1493
+ const amount = value[entry.resourceId] ?? 0;
1494
+ return typeof amount === "number" && Number.isInteger(amount) && amount >= (entry.min ?? 0) && amount <= (entry.max ?? Infinity);
1495
+ }) ? [] : ["Value is outside the current input domain."];
1496
+ }
1497
+ }
1498
+ function inputValueInDomain(domain, value, selection, options = {}) {
1499
+ return inputDomainErrors(domain, value, selection, options).length === 0;
1500
+ }
1501
+ function inputTargetInDomain(domain, value) {
1502
+ switch (domain.type) {
1503
+ case "choice":
1504
+ case "choiceList":
1505
+ return domain.choices.some(
1506
+ (choice) => !choice.disabled && Object.is(choice.value, value)
1507
+ );
1508
+ case "tileTarget":
1509
+ case "cardTarget":
1510
+ return typeof value === "string" && domain.eligibleTargets.includes(value);
1511
+ case "boardTarget":
1512
+ return domain.valueKind === "board-space" ? isBoardSpaceTarget(value) && domain.eligibleTargets.some(
1513
+ (candidate) => sameBoardSpaceTarget(candidate, value)
1514
+ ) : typeof value === "string" && domain.eligibleTargets.includes(value);
1515
+ case "zonePosition":
1516
+ return isPositionTarget(value) && domain.zones.some(
1517
+ (zone) => zone.zoneId === value.zoneId && zone.hostId === value.hostId && value.index <= zone.size
1518
+ );
1519
+ default:
1520
+ return inputValueInDomain(domain, value);
1521
+ }
1522
+ }
1523
+ function cardinalityErrors(value, min, max, unit, ignoreMinimum = false) {
1524
+ const errors = [];
1525
+ if (!ignoreMinimum && value.length < min)
1526
+ errors.push(`Choose at least ${min} ${pluralize(unit, min)}.`);
1527
+ if (max !== void 0 && value.length > max)
1528
+ errors.push(`Choose at most ${max} ${pluralize(unit, max)}.`);
1529
+ return errors;
1530
+ }
1531
+ function ineligibleMessage(domain) {
1532
+ return domain.type === "choice" || domain.type === "choiceList" ? "Selected choice is not eligible." : domain.type === "cardTarget" || domain.type === "tileTarget" || domain.type === "boardTarget" || domain.type === "zonePosition" ? "Selected target is not eligible." : "Value is outside the current input domain.";
1533
+ }
1534
+ function inputValueKey(value) {
1535
+ if (value === null) return "null";
1536
+ if (isBoardSpaceTarget(value))
1537
+ return `board-space:${JSON.stringify([value.boardId, value.spaceId])}`;
1538
+ if (isPositionTarget(value))
1539
+ return `position:${JSON.stringify([value.zoneId, value.hostId, value.index])}`;
1540
+ switch (typeof value) {
1541
+ case "string":
1542
+ return `string:${value}`;
1543
+ case "number":
1544
+ case "boolean":
1545
+ case "undefined":
1546
+ return `${typeof value}:${String(value)}`;
1547
+ default:
1548
+ return `json:${JSON.stringify(value)}`;
1549
+ }
1550
+ }
1551
+ function pluralize(word, count) {
1552
+ return count === 1 ? word : `${word}s`;
1553
+ }
1554
+
1555
+ export {
1556
+ __export,
1557
+ PositionTargetSchema,
1558
+ isPositionTarget,
1559
+ zonePositions,
1560
+ immutableCopy,
1561
+ GENERATED_ID_PREFIX,
1562
+ perPlayerInstanceId,
1563
+ parsePerPlayerInstanceId,
1564
+ perPlayerInstanceSchema,
1565
+ boardEdgeId,
1566
+ boardVertexId,
1567
+ parseBoardElementId,
1568
+ tileSpaceId,
1569
+ parseTileSpaceId,
1570
+ MAXIMUM_BOARD_COORDINATE,
1571
+ createHexTopology,
1572
+ createHexTopologyCache,
1573
+ RuntimeJsonSchema,
1574
+ canonicalizePluginRuntimeJson,
1575
+ encodeCanonicalPluginRuntimeJson,
1576
+ digestPluginRuntimeJson,
1577
+ BoardTopologyError,
1578
+ deriveBoardTopology,
1579
+ createBoardTopologyCache,
1580
+ getPublicTileFootprint,
1581
+ BoardRelationSchema,
1582
+ isPlayerIdValue,
1583
+ PlayerIdSchema,
1584
+ PlayerRosterSchema,
1585
+ AuthoredTileDisclosureSchema,
1586
+ TileDisclosureSchema,
1587
+ SeatTileRefSchema,
1588
+ SeatSpaceRefSchema,
1589
+ TilePlacementSchema,
1590
+ ProjectedTileSchema,
1591
+ SeatBoardTopologySchema,
1592
+ BoardProjectionSchema,
1593
+ requireLookup,
1594
+ BoardSpaceTargetSchema,
1595
+ isBoardSpaceTarget,
1596
+ sameBoardSpaceTarget,
1597
+ inputDomainErrors,
1598
+ inputValueInDomain,
1599
+ inputTargetInDomain,
1600
+ inputValueKey
1601
+ };
1602
+ //# sourceMappingURL=chunk-CZRPUWCT.js.map