@dreamboard-games/workspace-codegen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +89 -0
- package/NOTICE +1 -0
- package/dist/hex-geometry.d.ts +2 -0
- package/dist/hex-geometry.js +49 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +22 -0
- package/dist/manifest-contract.d.ts +14 -0
- package/dist/manifest-contract.js +4897 -0
- package/dist/manifest-validation.d.ts +6 -0
- package/dist/manifest-validation.js +506 -0
- package/dist/ownership.d.ts +31 -0
- package/dist/ownership.js +86 -0
- package/dist/preset-card-sets.d.ts +5 -0
- package/dist/preset-card-sets.js +135 -0
- package/dist/seeds.d.ts +6 -0
- package/dist/seeds.js +766 -0
- package/ownership.json +51 -0
- package/package.json +46 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-extra-key.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-missing-required.ts +60 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-enum.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-nested.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-scalar.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-visibility.ts +40 -0
- package/src/__fixtures__/sdk-types/invalid-container-card-set-manifest.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-array-item.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-player-id.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-resource-id.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-home-per-player-zone-no-owner.ts +35 -0
- package/src/__fixtures__/sdk-types/invalid-die-seed-type-id.ts +31 -0
- package/src/__fixtures__/sdk-types/invalid-die-visibility.ts +28 -0
- package/src/__fixtures__/sdk-types/invalid-generic-board-edge-id.ts +38 -0
- package/src/__fixtures__/sdk-types/invalid-generic-board-nested-field.ts +45 -0
- package/src/__fixtures__/sdk-types/invalid-hex-edge-field-edge-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-hex-vertex-field-vertex-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-manifest.ts +143 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-extra-key.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-card-id.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-enum.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-scalar.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-zone-id.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-container-no-owner.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-edge-no-owner.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-space-no-owner.ts +42 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-vertex-no-owner.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-piece-seed-type-id.ts +30 -0
- package/src/__fixtures__/sdk-types/invalid-piece-visibility.ts +28 -0
- package/src/__fixtures__/sdk-types/invalid-slot-host-manifest.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-slot-id-manifest.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-edge-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-space-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-vertex-id.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-container-field-space-id.ts +59 -0
- package/src/__fixtures__/sdk-types/invalid-square-container-host-space-id.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-relation-field-scalar.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-square-relation-space-id.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-square-space-fields-enum.ts +44 -0
- package/src/__fixtures__/sdk-types/invalid-square-space-fields-extra-key.ts +45 -0
- package/src/__fixtures__/sdk-types/valid-die-type-omits-sides.ts +29 -0
- package/src/__fixtures__/sdk-types/valid-manifest-omits-board-templates.ts +19 -0
- package/src/__fixtures__/sdk-types/valid-manifest.ts +612 -0
- package/src/__fixtures__/sdk-types/valid-player-scoped-seed-homes.ts +59 -0
- package/src/authoring-benchmark.test.ts +362 -0
- package/src/hex-geometry.ts +69 -0
- package/src/index.ts +64 -0
- package/src/manifest-contract.test.ts +1764 -0
- package/src/manifest-contract.ts +6581 -0
- package/src/manifest-validation.test.ts +393 -0
- package/src/manifest-validation.ts +795 -0
- package/src/ownership.ts +127 -0
- package/src/preset-card-sets.ts +169 -0
- package/src/sdk-types-authoring.test.ts +361 -0
- package/src/seeds.ts +800 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import type { GameTopologyManifest } from "@dreamboard/sdk-types";
|
|
3
|
+
import { validateManifestAuthoring } from "./manifest-validation.js";
|
|
4
|
+
|
|
5
|
+
const BASE_MANIFEST: GameTopologyManifest = {
|
|
6
|
+
players: {
|
|
7
|
+
minPlayers: 2,
|
|
8
|
+
maxPlayers: 2,
|
|
9
|
+
optimalPlayers: 2,
|
|
10
|
+
},
|
|
11
|
+
cardSets: [],
|
|
12
|
+
zones: [],
|
|
13
|
+
boardTemplates: [],
|
|
14
|
+
boards: [],
|
|
15
|
+
pieceTypes: [],
|
|
16
|
+
pieceSeeds: [],
|
|
17
|
+
dieTypes: [],
|
|
18
|
+
dieSeeds: [],
|
|
19
|
+
resources: [],
|
|
20
|
+
setupOptions: [],
|
|
21
|
+
setupProfiles: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
test("validateManifestAuthoring rejects hex vertex refs that do not resolve to a shared corner", () => {
|
|
25
|
+
const validation = validateManifestAuthoring({
|
|
26
|
+
...BASE_MANIFEST,
|
|
27
|
+
boards: [
|
|
28
|
+
{
|
|
29
|
+
id: "hex-board",
|
|
30
|
+
name: "Hex Board",
|
|
31
|
+
layout: "hex",
|
|
32
|
+
scope: "shared",
|
|
33
|
+
spaces: [
|
|
34
|
+
{ id: "a", q: 0, r: 0 },
|
|
35
|
+
{ id: "b", q: 1, r: 0 },
|
|
36
|
+
{ id: "c", q: 2, r: 0 },
|
|
37
|
+
],
|
|
38
|
+
vertices: [
|
|
39
|
+
{
|
|
40
|
+
ref: {
|
|
41
|
+
spaces: ["a", "b", "c"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(validation.errors).toContain(
|
|
50
|
+
"manifest.boards[0].vertices[0].ref: Hex board 'hex-board' with spaces 'a, b, c' failed validation. Hex vertex ref spaces 'a, b, c' do not resolve to exactly one shared vertex.",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("validateManifestAuthoring accepts hex vertex refs that resolve after template space merge", () => {
|
|
55
|
+
const validation = validateManifestAuthoring({
|
|
56
|
+
...BASE_MANIFEST,
|
|
57
|
+
boardTemplates: [
|
|
58
|
+
{
|
|
59
|
+
id: "triad",
|
|
60
|
+
name: "Triad",
|
|
61
|
+
layout: "hex",
|
|
62
|
+
spaces: [
|
|
63
|
+
{ id: "a", q: 0, r: 0 },
|
|
64
|
+
{ id: "b", q: 1, r: 0 },
|
|
65
|
+
{ id: "c", q: 0, r: 1 },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
boards: [
|
|
70
|
+
{
|
|
71
|
+
id: "hex-board",
|
|
72
|
+
name: "Hex Board",
|
|
73
|
+
layout: "hex",
|
|
74
|
+
scope: "shared",
|
|
75
|
+
templateId: "triad",
|
|
76
|
+
spaces: [{ id: "a", q: 0, r: 0 }],
|
|
77
|
+
vertices: [
|
|
78
|
+
{
|
|
79
|
+
ref: {
|
|
80
|
+
spaces: ["a", "b", "c"],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(validation.errors).toEqual([]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("validateManifestAuthoring rejects slot-bearing piece seeds without explicit singleton ids", () => {
|
|
92
|
+
const validation = validateManifestAuthoring({
|
|
93
|
+
...BASE_MANIFEST,
|
|
94
|
+
pieceTypes: [
|
|
95
|
+
{
|
|
96
|
+
id: "player-mat",
|
|
97
|
+
name: "Player Mat",
|
|
98
|
+
slots: [{ id: "worker-rest" }],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
pieceSeeds: [
|
|
102
|
+
{
|
|
103
|
+
typeId: "player-mat",
|
|
104
|
+
count: 2,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(validation.errors).toContain(
|
|
110
|
+
"manifest.pieceSeeds[0].id: Piece seed for slot-bearing type 'player-mat' must declare an explicit id.",
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("validateManifestAuthoring rejects slot-bearing die seeds with count greater than one", () => {
|
|
115
|
+
const validation = validateManifestAuthoring({
|
|
116
|
+
...BASE_MANIFEST,
|
|
117
|
+
dieTypes: [
|
|
118
|
+
{
|
|
119
|
+
id: "die-holder",
|
|
120
|
+
name: "Die Holder",
|
|
121
|
+
sides: 6,
|
|
122
|
+
slots: [{ id: "staging" }],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
dieSeeds: [
|
|
126
|
+
{
|
|
127
|
+
id: "holder",
|
|
128
|
+
typeId: "die-holder",
|
|
129
|
+
count: 2,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(validation.errors).toContain(
|
|
135
|
+
"manifest.dieSeeds[0].count: Die seed 'holder' for slot-bearing type 'die-holder' must omit count or set it to 1.",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("validateManifestAuthoring accepts die types that omit sides", () => {
|
|
140
|
+
const validation = validateManifestAuthoring({
|
|
141
|
+
...BASE_MANIFEST,
|
|
142
|
+
dieTypes: [
|
|
143
|
+
{
|
|
144
|
+
id: "d6",
|
|
145
|
+
name: "D6",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
dieSeeds: [
|
|
149
|
+
{
|
|
150
|
+
id: "d6-a",
|
|
151
|
+
typeId: "d6",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(validation.errors).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("validateManifestAuthoring rejects invalid strict slot hosts and slot ids", () => {
|
|
160
|
+
const validation = validateManifestAuthoring({
|
|
161
|
+
...BASE_MANIFEST,
|
|
162
|
+
cardSets: [
|
|
163
|
+
{
|
|
164
|
+
id: "main",
|
|
165
|
+
name: "Main",
|
|
166
|
+
type: "manual",
|
|
167
|
+
cardSchema: {
|
|
168
|
+
properties: {},
|
|
169
|
+
},
|
|
170
|
+
cards: [
|
|
171
|
+
{
|
|
172
|
+
type: "ace",
|
|
173
|
+
name: "Ace",
|
|
174
|
+
count: 1,
|
|
175
|
+
properties: {},
|
|
176
|
+
home: {
|
|
177
|
+
type: "slot",
|
|
178
|
+
host: {
|
|
179
|
+
kind: "piece",
|
|
180
|
+
id: "missing-host",
|
|
181
|
+
},
|
|
182
|
+
slotId: "worker-rest",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
pieceTypes: [
|
|
189
|
+
{
|
|
190
|
+
id: "player-mat",
|
|
191
|
+
name: "Player Mat",
|
|
192
|
+
slots: [{ id: "worker-rest" }],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
pieceSeeds: [
|
|
196
|
+
{
|
|
197
|
+
id: "mat-alpha",
|
|
198
|
+
typeId: "player-mat",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
dieTypes: [
|
|
202
|
+
{
|
|
203
|
+
id: "die-holder",
|
|
204
|
+
name: "Die Holder",
|
|
205
|
+
sides: 6,
|
|
206
|
+
slots: [{ id: "staging" }],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "d6",
|
|
210
|
+
name: "D6",
|
|
211
|
+
sides: 6,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
dieSeeds: [
|
|
215
|
+
{
|
|
216
|
+
id: "holder-a",
|
|
217
|
+
typeId: "die-holder",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "d6-a",
|
|
221
|
+
typeId: "d6",
|
|
222
|
+
home: {
|
|
223
|
+
type: "slot",
|
|
224
|
+
host: {
|
|
225
|
+
kind: "die",
|
|
226
|
+
id: "holder-a",
|
|
227
|
+
},
|
|
228
|
+
slotId: "missing-slot",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(validation.errors).toContain(
|
|
235
|
+
"manifest.cardSets[0].cards[0].home.host: Unknown strict slot host 'piece:missing-host'. Hosts must be singleton piece/die seeds whose type declares slots.",
|
|
236
|
+
);
|
|
237
|
+
expect(validation.errors).toContain(
|
|
238
|
+
"manifest.dieSeeds[1].home.slotId: Unknown slot 'missing-slot' for host 'die:holder-a'.",
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("validateManifestAuthoring rejects player-scoped seed homes without ownerId", () => {
|
|
243
|
+
const validation = validateManifestAuthoring({
|
|
244
|
+
...BASE_MANIFEST,
|
|
245
|
+
zones: [
|
|
246
|
+
{
|
|
247
|
+
id: "scout-hand",
|
|
248
|
+
name: "Scout Hand",
|
|
249
|
+
scope: "perPlayer",
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
boards: [
|
|
253
|
+
{
|
|
254
|
+
id: "player-mat",
|
|
255
|
+
name: "Player Mat",
|
|
256
|
+
layout: "square",
|
|
257
|
+
scope: "perPlayer",
|
|
258
|
+
spaces: [{ id: "camp", row: 0, col: 0 }],
|
|
259
|
+
relations: [],
|
|
260
|
+
containers: [],
|
|
261
|
+
edges: [],
|
|
262
|
+
vertices: [],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
pieceTypes: [{ id: "meeple", name: "Meeple" }],
|
|
266
|
+
pieceSeeds: [
|
|
267
|
+
{
|
|
268
|
+
id: "worker-a",
|
|
269
|
+
typeId: "meeple",
|
|
270
|
+
home: {
|
|
271
|
+
type: "space",
|
|
272
|
+
boardId: "player-mat",
|
|
273
|
+
spaceId: "camp",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
dieTypes: [{ id: "d6", name: "D6", sides: 6 }],
|
|
278
|
+
dieSeeds: [
|
|
279
|
+
{
|
|
280
|
+
id: "die-a",
|
|
281
|
+
typeId: "d6",
|
|
282
|
+
home: {
|
|
283
|
+
type: "zone",
|
|
284
|
+
zoneId: "scout-hand",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(validation.errors).toContain(
|
|
291
|
+
"manifest.pieceSeeds[0].home.boardId: Piece seed 'worker-a' requires ownerId because board 'player-mat' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.",
|
|
292
|
+
);
|
|
293
|
+
expect(validation.errors).toContain(
|
|
294
|
+
"manifest.dieSeeds[0].home.zoneId: Die seed 'die-a' requires ownerId because zone 'scout-hand' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.",
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("validateManifestAuthoring accepts player-scoped seed homes with ownerId", () => {
|
|
299
|
+
const validation = validateManifestAuthoring({
|
|
300
|
+
...BASE_MANIFEST,
|
|
301
|
+
zones: [
|
|
302
|
+
{
|
|
303
|
+
id: "scout-hand",
|
|
304
|
+
name: "Scout Hand",
|
|
305
|
+
scope: "perPlayer",
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
boards: [
|
|
309
|
+
{
|
|
310
|
+
id: "player-mat",
|
|
311
|
+
name: "Player Mat",
|
|
312
|
+
layout: "square",
|
|
313
|
+
scope: "perPlayer",
|
|
314
|
+
spaces: [{ id: "camp", row: 0, col: 0 }],
|
|
315
|
+
relations: [],
|
|
316
|
+
containers: [],
|
|
317
|
+
edges: [],
|
|
318
|
+
vertices: [],
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
pieceTypes: [{ id: "meeple", name: "Meeple" }],
|
|
322
|
+
pieceSeeds: [
|
|
323
|
+
{
|
|
324
|
+
id: "worker-a",
|
|
325
|
+
typeId: "meeple",
|
|
326
|
+
ownerId: "player-1",
|
|
327
|
+
home: {
|
|
328
|
+
type: "space",
|
|
329
|
+
boardId: "player-mat",
|
|
330
|
+
spaceId: "camp",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
dieTypes: [{ id: "d6", name: "D6", sides: 6 }],
|
|
335
|
+
dieSeeds: [
|
|
336
|
+
{
|
|
337
|
+
id: "die-a",
|
|
338
|
+
typeId: "d6",
|
|
339
|
+
ownerId: "player-1",
|
|
340
|
+
home: {
|
|
341
|
+
type: "zone",
|
|
342
|
+
zoneId: "scout-hand",
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
expect(validation.errors).toEqual([]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("validateManifestAuthoring warns when board-scoped category type ids are ambiguous across boards", () => {
|
|
352
|
+
const validation = validateManifestAuthoring({
|
|
353
|
+
...BASE_MANIFEST,
|
|
354
|
+
boards: [
|
|
355
|
+
{
|
|
356
|
+
id: "alpha",
|
|
357
|
+
name: "Alpha",
|
|
358
|
+
layout: "hex",
|
|
359
|
+
scope: "shared",
|
|
360
|
+
spaces: [{ id: "a", q: 0, r: 0, typeId: "site" }],
|
|
361
|
+
edges: [
|
|
362
|
+
{
|
|
363
|
+
ref: { spaces: ["a"] },
|
|
364
|
+
typeId: "route",
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
vertices: [],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: "beta",
|
|
371
|
+
name: "Beta",
|
|
372
|
+
layout: "hex",
|
|
373
|
+
scope: "shared",
|
|
374
|
+
spaces: [{ id: "b", q: 0, r: 0, typeId: "site" }],
|
|
375
|
+
edges: [
|
|
376
|
+
{
|
|
377
|
+
ref: { spaces: ["b"] },
|
|
378
|
+
typeId: "route",
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
vertices: [],
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(validation.errors).toEqual([]);
|
|
387
|
+
expect(validation.warnings).toContain(
|
|
388
|
+
"Ambiguous space.typeId 'site' is authored on multiple boards (alpha, beta). Prefer boardHelpers.spaceIdsByBoardId / boardHelpers.spaceTypeIdByBoardId for board-scoped lookups.",
|
|
389
|
+
);
|
|
390
|
+
expect(validation.warnings).toContain(
|
|
391
|
+
"Ambiguous edge.typeId 'route' is authored on multiple boards (alpha, beta). Prefer boardHelpers.edgeIdsByBoardIdAndTypeId for board-scoped lookups.",
|
|
392
|
+
);
|
|
393
|
+
});
|