dreamboard 0.1.7 → 0.1.8

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 (92) hide show
  1. package/LICENSE +89 -0
  2. package/NOTICE +1 -0
  3. package/README.md +22 -178
  4. package/dist/{chunk-MP7IBNWW.js → chunk-BMYC6772.js} +577 -38
  5. package/dist/chunk-BMYC6772.js.map +1 -0
  6. package/dist/chunk-D4HDZEJT.js +37 -0
  7. package/dist/chunk-EM7NOFFC.js +19072 -0
  8. package/dist/chunk-EM7NOFFC.js.map +1 -0
  9. package/dist/chunk-SET56RVA.js +435 -0
  10. package/dist/chunk-SET56RVA.js.map +1 -0
  11. package/dist/{dist-B3R64F6G.js → dist-WJRJNFLI.js} +3 -3
  12. package/dist/global-config-VQJZ3JOA.js +15 -0
  13. package/dist/global-config-VQJZ3JOA.js.map +1 -0
  14. package/dist/index.js +23993 -24387
  15. package/dist/index.js.map +1 -1
  16. package/dist/keychain-backend-2DL3C6BS.js +125 -0
  17. package/dist/keychain-backend-2DL3C6BS.js.map +1 -0
  18. package/dist/{prompt-GMZABCJC.js → prompt-IV67HSPU.js} +2 -2
  19. package/dist/scaffold/assets/static/app/tsconfig.framework.json +24 -0
  20. package/dist/scaffold/assets/static/app/tsconfig.json +3 -0
  21. package/dist/scaffold/assets/static/ui/App.tsx +22 -0
  22. package/dist/scaffold/assets/static/ui/index.tsx +12 -0
  23. package/dist/scaffold/assets/static/ui/style.css +4 -0
  24. package/dist/scaffold/assets/static/ui/tsconfig.framework.json +31 -0
  25. package/dist/scaffold/assets/static/ui/tsconfig.json +5 -0
  26. package/dist/src-K2NK3A4P.js +626 -0
  27. package/dist/src-K2NK3A4P.js.map +1 -0
  28. package/package.json +41 -4
  29. package/skills/dreamboard/SKILL.md +126 -59
  30. package/skills/dreamboard/references/authoring-lifecycle.md +102 -0
  31. package/skills/dreamboard/references/board-surfaces.md +36 -0
  32. package/skills/dreamboard/references/board-topology.md +443 -0
  33. package/skills/dreamboard/references/boards-and-topology.md +100 -0
  34. package/skills/dreamboard/references/card-actions.md +107 -0
  35. package/skills/dreamboard/references/cli.md +120 -0
  36. package/skills/dreamboard/references/core-concepts.md +514 -0
  37. package/skills/dreamboard/references/custom-renderers.md +26 -0
  38. package/skills/dreamboard/references/derived-values.md +55 -0
  39. package/skills/dreamboard/references/effects.md +111 -0
  40. package/skills/dreamboard/references/game-contract.md +89 -0
  41. package/skills/dreamboard/references/game-definition.md +89 -0
  42. package/skills/dreamboard/references/game-shell.md +80 -0
  43. package/skills/dreamboard/references/hand-surfaces.md +33 -0
  44. package/skills/dreamboard/references/index.md +112 -0
  45. package/skills/dreamboard/references/inputs-and-targets.md +160 -0
  46. package/skills/dreamboard/references/interactions.md +158 -0
  47. package/skills/dreamboard/references/manifest-fields.md +137 -0
  48. package/skills/dreamboard/references/manifest.md +81 -0
  49. package/skills/dreamboard/references/package-surfaces.md +69 -0
  50. package/skills/dreamboard/references/phases.md +145 -0
  51. package/skills/dreamboard/references/prompts-and-choices.md +55 -0
  52. package/skills/dreamboard/references/quickstart.md +71 -0
  53. package/skills/dreamboard/references/rule-authoring.md +81 -77
  54. package/skills/dreamboard/references/setup-bootstrap.md +78 -0
  55. package/skills/dreamboard/references/stages-and-zones.md +59 -0
  56. package/skills/dreamboard/references/static-views.md +67 -0
  57. package/skills/dreamboard/references/table-queries-and-ops.md +103 -0
  58. package/skills/dreamboard/references/testing-bases.md +147 -0
  59. package/skills/dreamboard/references/testing-generated-contracts.md +142 -0
  60. package/skills/dreamboard/references/testing-runtime-assertions.md +124 -0
  61. package/skills/dreamboard/references/testing-scenarios.md +148 -0
  62. package/skills/dreamboard/references/testing-ui-tests.md +174 -0
  63. package/skills/dreamboard/references/testing.md +161 -0
  64. package/skills/dreamboard/references/ui-architecture.md +137 -0
  65. package/skills/dreamboard/references/ui-components.md +34 -0
  66. package/skills/dreamboard/references/views.md +72 -0
  67. package/skills/dreamboard/references/workspace-layout.md +136 -0
  68. package/dist/chunk-2H7UOFLK.js +0 -11
  69. package/dist/chunk-MP7IBNWW.js.map +0 -1
  70. package/dist/chunk-WM7TY7DV.js +0 -2328
  71. package/dist/chunk-WM7TY7DV.js.map +0 -1
  72. package/dist/embedded-harness-IHP7A2ZX.js +0 -344
  73. package/dist/embedded-harness-IHP7A2ZX.js.map +0 -1
  74. package/skills/dreamboard/references/adversarial-testing.md +0 -113
  75. package/skills/dreamboard/references/all-players-tracking.md +0 -75
  76. package/skills/dreamboard/references/api-reference.md +0 -197
  77. package/skills/dreamboard/references/app-best-practices.md +0 -98
  78. package/skills/dreamboard/references/building-your-first-game.md +0 -49
  79. package/skills/dreamboard/references/hands-vs-decks.md +0 -124
  80. package/skills/dreamboard/references/manifest-authoring.md +0 -328
  81. package/skills/dreamboard/references/phase-handlers.md +0 -134
  82. package/skills/dreamboard/references/scenario-format.md +0 -99
  83. package/skills/dreamboard/references/test-harness.md +0 -225
  84. package/skills/dreamboard/references/ui-best-practices.md +0 -156
  85. package/skills/dreamboard/references/ui-genre-resource-management.md +0 -187
  86. package/skills/dreamboard/references/ui-genre-trick-taking.md +0 -110
  87. package/skills/dreamboard/references/ui-genre-worker-placement.md +0 -143
  88. package/skills/dreamboard/references/ui-style-guide.md +0 -54
  89. package/skills/dreamboard/scripts/events-extract.mjs +0 -218
  90. /package/dist/{chunk-2H7UOFLK.js.map → chunk-D4HDZEJT.js.map} +0 -0
  91. /package/dist/{dist-B3R64F6G.js.map → dist-WJRJNFLI.js.map} +0 -0
  92. /package/dist/{prompt-GMZABCJC.js.map → prompt-IV67HSPU.js.map} +0 -0
@@ -0,0 +1,443 @@
1
+ # Board topology
2
+
3
+ Author hex and square boards with shared reducer and UI topology primitives.
4
+
5
+ Use tiled boards when your game has stable spaces plus meaningful borders or
6
+ corners.
7
+
8
+ Dreamboard currently supports two fixed-topology tiled layouts:
9
+
10
+ - `layout: "hex"` for axial hex maps
11
+ - `layout: "square"` for row and column grids
12
+
13
+ Keep `layout: "generic"` for authored graphs that are not tile-based, such as
14
+ tracks, networks, or map nodes.
15
+
16
+ It is normal for one manifest to mix these layouts. For example, a game can use
17
+ one shared generic market board, one shared hex frontier map, and one
18
+ per-player square mat without splitting that coverage across multiple
19
+ workspaces.
20
+
21
+ The generated manifest contract now preserves that mixed topology directly with
22
+ template-level literals plus board-scoped edge and vertex helpers:
23
+
24
+ {/* Generated from examples/board-contract-lab/app/authoring-benchmark-typing-smoke.ts (tiled-board-helpers) */}
25
+
26
+ ```ts
27
+ const playerMatId = boardHelpers.boardIdForPlayer("player-mat", "player-1");
28
+ const hexBoardIds = boardHelpers.boardIdsForLayout("hex");
29
+ const frontierRouteId = boardHelpers.edgeIds("frontier-map", "ferry-route")[0]!;
30
+ const frontierCheckpointId = boardHelpers.vertexIds(
31
+ "frontier-map",
32
+ "checkpoint",
33
+ )[0]!;
34
+ const marketTemplateBoards =
35
+ boardHelpers.boardBaseIdsForTemplate("market-template");
36
+ const playerMatTemplateLayout = boardHelpers.boardTemplateLayout(
37
+ "player-mat-template",
38
+ );
39
+ //
40
+ ```
41
+
42
+ ## What lives where
43
+
44
+ Put immutable topology in the manifest:
45
+
46
+ - spaces and their coordinates
47
+ - optional authored edge and vertex metadata
48
+ - authored containers and extra relations
49
+
50
+ Put mutable gameplay state in reducer state:
51
+
52
+ - ownership keyed by `spaceId`, `edgeId`, or `vertexId`
53
+ - placed tiles, markers, units, or hazards
54
+ - highlights, legal targets, and temporary UI state
55
+
56
+ That split keeps the board reusable across different games and avoids treating
57
+ manifest topology as a mutable gameplay store.
58
+
59
+ ## Fixed-topology constraint
60
+
61
+ Hex and square boards in this release are fixed once the session starts.
62
+
63
+ - Terraforming Mars fits this model: spaces stay fixed while ownership and tile
64
+ state change.
65
+ - Gloomhaven fits this model: spaces stay fixed while units move and range is
66
+ recalculated.
67
+ - Cascadia fits this model when the habitat map is pre-authored and scoring
68
+ reads adjacency from the fixed board.
69
+
70
+ Carcassonne-style board growth is not part of this release. Treat that as a
71
+ future topology adapter rather than part of the current `square` layout.
72
+
73
+ ## Shared topology vocabulary
74
+
75
+ Reducer helpers and UI hooks use the same terms:
76
+
77
+ | Term | Meaning |
78
+ | ----------- | ----------------------------------------------------- |
79
+ | `space` | One tile or cell on the board |
80
+ | `edge` | One border shared by one or two spaces |
81
+ | `vertex` | One corner touched by one to four spaces |
82
+ | `adjacent` | The default movement or scoring graph for a layout |
83
+ | `incident` | Edges touching a vertex, or vertices touching an edge |
84
+ | `occupancy` | Components currently on a space, edge, or vertex |
85
+
86
+ Reducer-side board helpers are covered in [Table queries and ops](./table-queries-and-ops.md).
87
+ UI-side board rendering is covered in [Board targets](./board-surfaces.md)
88
+ and [UI components](./ui-components.md).
89
+
90
+ ## Choosing a layout
91
+
92
+ | Layout | Use it for | Default adjacency |
93
+ | --------- | ----------------------------------------------------- | ---------------------------------- |
94
+ | `generic` | Tracks, networks, arbitrary relation graphs | Whatever you author in `relations` |
95
+ | `hex` | Terraforming Mars, Gloomhaven, Cascadia, wargame maps | Derived hex adjacency |
96
+ | `square` | Chess, Checkers, Onitama, fixed tile grids | Orthogonal 4-way adjacency |
97
+
98
+ Square boards can still use diagonal logic. The reducer and UI helpers expose
99
+ square-specific variants for diagonal neighbors and Chebyshev distance.
100
+
101
+ ## Reducer state pattern
102
+
103
+ Store dynamic tiled state in plain overlay maps keyed by stable location IDs.
104
+
105
+ ```ts
106
+ import { z } from "zod";
107
+ import type {
108
+ TiledEdgeMap,
109
+ TiledSpaceMap,
110
+ TiledVertexMap,
111
+ } from "@dreamboard/app-sdk/reducer";
112
+ import { ids } from "../shared/manifest-contract";
113
+
114
+ const publicState = z.object({
115
+ ownershipBySpaceId: z.partialRecord(ids.spaceId, ids.playerId).default({}),
116
+ effectByEdgeId: z.partialRecord(ids.edgeId, z.string()).default({}),
117
+ markerByVertexId: z
118
+ .partialRecord(
119
+ ids.vertexId,
120
+ z.object({
121
+ ownerId: ids.playerId,
122
+ kind: z.string(),
123
+ }),
124
+ )
125
+ .default({}),
126
+ });
127
+
128
+ type OwnershipBySpaceId = TiledSpaceMap<
129
+ typeof manifestContract.table,
130
+ "mars",
131
+ string
132
+ >;
133
+ type EffectByEdgeId = TiledEdgeMap<
134
+ typeof manifestContract.table,
135
+ "arena",
136
+ string
137
+ >;
138
+ type MarkerByVertexId = TiledVertexMap<
139
+ typeof manifestContract.table,
140
+ "arena",
141
+ { ownerId: string; kind: string }
142
+ >;
143
+ ```
144
+
145
+ Use `z.partialRecord(...)` for mutable tiled overlays. Runtime gameplay state is
146
+ usually sparse, so empty-object initialization should compile and missing keys
147
+ should mean "nothing placed here yet".
148
+
149
+ Use this pattern for:
150
+
151
+ - Terraforming Mars: `ownershipBySpaceId`, placed tile type, adjacency-bonus flags
152
+ - Gloomhaven: movement blockers by edge, trap markers by vertex, units keyed by
153
+ space
154
+ - Cascadia: adjacency-driven scoring keyed by placed habitat or wildlife tiles
155
+
156
+ ## Projecting UI-ready boards
157
+
158
+ Reducer views should join immutable topology with mutable overlay state before
159
+ rendering. Destructure the injected `q` inside `project({ state, playerId, q })`
160
+ instead of constructing a fresh query from state.
161
+
162
+ ```ts
163
+ import { defineView } from "@dreamboard/app-sdk/reducer";
164
+ import type { GameContract } from "./game-contract";
165
+
166
+ export const playerView = defineView<GameContract>()({
167
+ project({ state, q }) {
168
+ const board = q.board.tiled("arena");
169
+
170
+ return {
171
+ boardId: board.id,
172
+ layout: board.layout,
173
+ spaces: Object.values(board.spaces).map((space) => ({
174
+ ...space,
175
+ ownerId: state.publicState.ownershipBySpaceId[space.id] ?? null,
176
+ edgeIds: q.board.spaceEdges(board.id, space.id),
177
+ vertexIds: q.board.spaceVertices(board.id, space.id),
178
+ })),
179
+ legalEdgeIds: state.publicState.legalEdgeIds,
180
+ legalVertexIds: state.publicState.legalVertexIds,
181
+ };
182
+ },
183
+ });
184
+ ```
185
+
186
+ Keep canonical legality and scoring in reducer code. The UI should consume a
187
+ projected board view plus simple highlight or legal-target sets.
188
+
189
+ ## Board shape and traversal
190
+
191
+ - `board.spaces` is an ID-keyed record.
192
+ - `board.edges` is an array of edge records.
193
+ - `board.vertices` is an array of vertex records.
194
+
195
+ <Warning>
196
+ Do not use `Object.keys(board.edges)` or `Object.keys(board.vertices)` to
197
+ discover runtime IDs. Those expressions return array indexes, not edge or
198
+ vertex IDs.
199
+ </Warning>
200
+
201
+ Use reducer-native query helpers when you need topology traversal:
202
+
203
+ - `q.board.spaceEdges(boardId, spaceId)`
204
+ - `q.board.spaceVertices(boardId, spaceId)`
205
+ - `q.board.incidentEdges(boardId, vertexId)`
206
+ - `q.board.incidentVertices(boardId, edgeId)`
207
+
208
+ `incidentEdges(...)` and `incidentVertices(...)` are geometry helpers. They
209
+ return all tiled edges or vertices that share the same incident spaces as the
210
+ input location. They do not apply gameplay-specific filters such as road
211
+ ownership or blocked-path rules.
212
+
213
+ ## Wiring the UI
214
+
215
+ Use one of the layout-specific renderers:
216
+
217
+ - `HexGrid` for hex spaces, edges, and vertices
218
+ - `SquareGrid` for square cells, edges, and vertices
219
+
220
+ Use the headless topology hooks when the UI needs geometry-aware logic:
221
+
222
+ - `useBoardTopology(...)` for shared tiled queries
223
+ - `useHexBoard(...)` for hex-specific coordinate helpers
224
+ - `useSquareBoard(...)` for square-specific neighbor and distance variants
225
+
226
+ ```tsx
227
+ import { HexGrid, SquareGrid, useBoardTopology, useSquareBoard } from "@dreamboard/ui-sdk";
228
+
229
+ function ArenaBoard({ board, onEdgeClick, onVertexClick }) {
230
+ const topology = useSquareBoard(board);
231
+
232
+ return (
233
+ <SquareGrid
234
+ board={board}
235
+ interactiveEdges={{ selectTargetId: onEdgeClick }}
236
+ interactiveVertices={{ selectTargetId: onVertexClick }}
237
+ renderCell={(row, col) => {
238
+ const cell = topology.getCellAt(row, col);
239
+ return <Cell ownerId={cell?.owner ?? null} />;
240
+ }}
241
+ renderPiece={(piece) => <Piece piece={piece} />}
242
+ />
243
+ );
244
+ }
245
+ ```
246
+
247
+ This keeps interactivity consistent across layouts. Authors do not need to
248
+ re-derive borders, corners, or adjacency in UI code.
249
+
250
+ Project the whole tiled board record into the reducer view. Then keep
251
+ game-specific legality beside it:
252
+
253
+ ```ts
254
+ project({ state, q }) {
255
+ return {
256
+ board: q.board.tiled("arena"),
257
+ legalEdgeIds: state.publicState.legalEdgeIds,
258
+ legalVertexIds: state.publicState.legalVertexIds,
259
+ };
260
+ }
261
+ ```
262
+
263
+ Use this rule when deciding what to expose:
264
+
265
+ - `spaces` are enough for tile or cell rendering only
266
+ - `edges` are required when the UI should render or interact with borders
267
+ - `vertices` are required when the UI should render or interact with corners
268
+
269
+ For example, a route-building board view should project all three so the UI can
270
+ render paths and markers and also expose placement targets.
271
+
272
+ The important reducer-native detail is that target-layer IDs keep the board's
273
+ exact runtime types. If the reducer view projects typed `edge.id` and
274
+ `vertex.id` values, `board={board}` preserves those same ids through
275
+ `selectTargetId` instead of forcing the UI to remap topology ids by hand.
276
+
277
+ ## Runtime IDs
278
+
279
+ Hex and square edge and vertex IDs are derived runtime IDs based on authored
280
+ space topology.
281
+
282
+ - author spaces in the manifest
283
+ - optionally attach authored edge and vertex metadata by `ref.spaces`
284
+ - read the runtime `edgeId` and `vertexId` values from reducer helpers,
285
+ `boardHelpers.edgeIds(...)`, `boardHelpers.vertexIds(...)`,
286
+ `boardHelpers.boardIdForPlayer(...)`, or the projected board view
287
+ - use those IDs as keys in reducer state and as click targets in the UI
288
+
289
+ <Warning>
290
+ Do not hardcode the derived string format in app code. Read the runtime
291
+ `edgeId` and `vertexId` values from the helpers and projected board view
292
+ above instead.
293
+ </Warning>
294
+
295
+ ## Valid and invalid tiled refs
296
+
297
+ Author edge and vertex refs with real shared geometry, not just “nearby”
298
+ spaces. These examples show the difference.
299
+
300
+ ### Square edge refs
301
+
302
+ Valid square edge ref: the two cells share one border.
303
+
304
+ ```ts
305
+ {
306
+ layout: "square",
307
+ spaces: [
308
+ { id: "a1", row: 0, col: 0 },
309
+ { id: "a2", row: 0, col: 1 },
310
+ ],
311
+ edges: [
312
+ { ref: { spaces: ["a1", "a2"] }, typeId: "road" },
313
+ ],
314
+ }
315
+ ```
316
+
317
+ Invalid square edge ref: diagonal cells do not share an edge.
318
+
319
+ ```ts
320
+ {
321
+ layout: "square",
322
+ spaces: [
323
+ { id: "a1", row: 0, col: 0 },
324
+ { id: "b2", row: 1, col: 1 },
325
+ ],
326
+ edges: [
327
+ { ref: { spaces: ["a1", "b2"] }, typeId: "road" },
328
+ ],
329
+ }
330
+ ```
331
+
332
+ Why invalid: `a1` and `b2` only meet at a corner, so this must be a vertex ref
333
+ instead of an edge ref.
334
+
335
+ ### Square vertex refs
336
+
337
+ Valid square vertex ref: the cells resolve to one shared corner.
338
+
339
+ ```ts
340
+ {
341
+ layout: "square",
342
+ spaces: [
343
+ { id: "a1", row: 0, col: 0 },
344
+ { id: "a2", row: 0, col: 1 },
345
+ { id: "b1", row: 1, col: 0 },
346
+ { id: "b2", row: 1, col: 1 },
347
+ ],
348
+ vertices: [
349
+ { ref: { spaces: ["a1", "a2", "b1", "b2"] }, typeId: "tower" },
350
+ ],
351
+ }
352
+ ```
353
+
354
+ Invalid square vertex ref: the cells do not collapse to exactly one corner.
355
+
356
+ ```ts
357
+ {
358
+ layout: "square",
359
+ spaces: [
360
+ { id: "a1", row: 0, col: 0 },
361
+ { id: "a2", row: 0, col: 1 },
362
+ { id: "c1", row: 2, col: 0 },
363
+ ],
364
+ vertices: [
365
+ { ref: { spaces: ["a1", "a2", "c1"] }, typeId: "tower" },
366
+ ],
367
+ }
368
+ ```
369
+
370
+ Why invalid: those three cells do not touch the same corner, so the ref cannot
371
+ resolve to one runtime vertex ID.
372
+
373
+ ### Hex edge refs
374
+
375
+ Valid hex edge ref: the two hexes share one border.
376
+
377
+ ```ts
378
+ {
379
+ layout: "hex",
380
+ spaces: [
381
+ { id: "hex-a", q: 0, r: 0 },
382
+ { id: "hex-b", q: 1, r: 0 },
383
+ ],
384
+ edges: [
385
+ { ref: { spaces: ["hex-a", "hex-b"] }, typeId: "road" },
386
+ ],
387
+ }
388
+ ```
389
+
390
+ Invalid hex edge ref: non-adjacent hexes do not form an edge.
391
+
392
+ ```ts
393
+ {
394
+ layout: "hex",
395
+ spaces: [
396
+ { id: "hex-a", q: 0, r: 0 },
397
+ { id: "hex-c", q: 2, r: 0 },
398
+ ],
399
+ edges: [
400
+ { ref: { spaces: ["hex-a", "hex-c"] }, typeId: "road" },
401
+ ],
402
+ }
403
+ ```
404
+
405
+ Why invalid: `hex-a` and `hex-c` are separated by another hex, so there is no
406
+ shared border to attach an edge to.
407
+
408
+ ### Hex vertex refs
409
+
410
+ Valid hex vertex ref: the three hexes meet at one corner.
411
+
412
+ ```ts
413
+ {
414
+ layout: "hex",
415
+ spaces: [
416
+ { id: "hex-a", q: 0, r: 0 },
417
+ { id: "hex-b", q: 1, r: 0 },
418
+ { id: "hex-c", q: 0, r: 1 },
419
+ ],
420
+ vertices: [
421
+ { ref: { spaces: ["hex-a", "hex-b", "hex-c"] }, typeId: "settlement" },
422
+ ],
423
+ }
424
+ ```
425
+
426
+ Invalid hex vertex ref: collinear hexes do not share a single corner.
427
+
428
+ ```ts
429
+ {
430
+ layout: "hex",
431
+ spaces: [
432
+ { id: "hex-a", q: 0, r: 0 },
433
+ { id: "hex-b", q: 1, r: 0 },
434
+ { id: "hex-c", q: 2, r: 0 },
435
+ ],
436
+ vertices: [
437
+ { ref: { spaces: ["hex-a", "hex-b", "hex-c"] }, typeId: "settlement" },
438
+ ],
439
+ }
440
+ ```
441
+
442
+ Why invalid: those three hexes never meet at one point, so the ref fails
443
+ validation instead of producing an ambiguous runtime vertex.
@@ -0,0 +1,100 @@
1
+ # Boards and topology
2
+
3
+ Model generic, hex, and square boards with typed spaces, edges, vertices, containers, and board refs.
4
+
5
+ Boards are manifest-defined topology. They can be generic, hex, or square, shared or per-player, inline or based on a template.
6
+
7
+ ## Board layouts
8
+
9
+ | Layout | Use for |
10
+ | --- | --- |
11
+ | `generic` | Free-form spaces and relations, tracks, maps, abstract zones. |
12
+ | `hex` | Hex grids with spaces, edges, and vertices. |
13
+ | `square` | Square grids with rows, columns, spaces, edges, and vertices. |
14
+
15
+ All layouts can have spaces and containers. Tiled layouts also expose typed edge and vertex ids.
16
+
17
+ ## Inline board
18
+
19
+ ```ts
20
+ boards: [
21
+ {
22
+ id: "action-board",
23
+ name: "Action Board",
24
+ layout: "square",
25
+ scope: "shared",
26
+ spaces: [
27
+ { id: "plaza", row: 0, col: 0, typeId: "action" },
28
+ ],
29
+ },
30
+ ];
31
+ ```
32
+
33
+ Use inline boards when a board appears once. Use templates when several board instances share topology.
34
+
35
+ ## Templates
36
+
37
+ ```ts
38
+ boardTemplates: [
39
+ {
40
+ id: "market-template",
41
+ name: "Market Template",
42
+ layout: "generic",
43
+ spaces: [
44
+ { id: "auction", typeId: "action" },
45
+ { id: "harvest", typeId: "action" },
46
+ ],
47
+ },
48
+ ],
49
+ boards: [
50
+ {
51
+ id: "market-board",
52
+ name: "Market Board",
53
+ templateId: "market-template",
54
+ layout: "generic",
55
+ scope: "shared",
56
+ },
57
+ ];
58
+ ```
59
+
60
+ Template-backed boards still get their own board id. Reducer and UI code should address runtime board data by board id, not by template id.
61
+
62
+ ## Shared and per-player boards
63
+
64
+ `scope: "shared"` creates one board instance for the session. `scope: "perPlayer"` creates one board per player.
65
+
66
+ Use shared boards for central maps and markets. Use per-player boards for player mats, personal tracks, and private layouts.
67
+
68
+ ## Runtime board queries
69
+
70
+ Inside reducer callbacks, views, derived values, and target rules, read board data through injected `q`:
71
+
72
+ ```ts
73
+ const board = q.board.hex("island");
74
+ const edge = q.board.edge("island", edgeId);
75
+ const adjacent = q.board.incidentVertices("island", edgeId);
76
+ ```
77
+
78
+ Use `q.board.get` for layout-agnostic access, `q.board.hex` / `q.board.square` for layout-specific topology, and traversal helpers such as `incidentEdges`, `incidentVertices`, `spaceEdges`, `spaceVertices`, and `squareNeighbors` for rules.
79
+
80
+ ## Static board projection
81
+
82
+ Static board topology belongs in [Static views](./static-views.md), not in dynamic player views.
83
+
84
+ ```ts
85
+ export const boardStatic = defineStaticView<GameContract>()({
86
+ project: ({ q }) => {
87
+ const board = q.board.hex("island");
88
+ return {
89
+ ...board,
90
+ portsByVertexId: computePorts(board),
91
+ };
92
+ },
93
+ });
94
+ ```
95
+
96
+ Keep game-specific decoration local, but use SDK static board queries for reusable topology access.
97
+
98
+ ## Deep topology reference
99
+
100
+ For exact square/hex edge and vertex id rules, see [Board topology](./board-topology.md).
@@ -0,0 +1,107 @@
1
+ # Card actions
2
+
3
+ Use defineCardAction for first-class actions that play a card from a hand zone.
4
+
5
+ Use `defineCardAction` when the player action starts from a concrete card in a hand or card zone: play an action card, reveal a tactic, discard a reaction, trigger a card ability.
6
+
7
+ ```ts
8
+ import { cardTypes, zones } from "../shared/manifest-contract";
9
+
10
+ const playBoost = defineCardAction<
11
+ GameContract,
12
+ typeof playerTurnPhaseStateSchema
13
+ >()({
14
+ cardType: cardTypes.boost,
15
+ playFrom: zones.actionHand,
16
+ label: "Play boost",
17
+ step: "main",
18
+ inputs: {
19
+ selectedSpaceId: boardInput.space<GameState, SpaceId>({
20
+ target: selectableSpaceTarget,
21
+ }),
22
+ stealFromPlayerId: formInput(ids.playerId.nullable()),
23
+ },
24
+ rules: [
25
+ {
26
+ id: "can-play-action-card",
27
+ errorCode: "CARD_NOT_PLAYABLE",
28
+ validate: ({ state }) => validateCanPlayActionCard(state),
29
+ },
30
+ ],
31
+ reduce({ state, input, accept, ops }) {
32
+ return accept(
33
+ pipe(
34
+ state,
35
+ ops.moveCardFromPlayerZoneToSharedZone({
36
+ playerId: input.playerId,
37
+ fromZoneId: zones.actionHand,
38
+ toZoneId: zones.playedCards,
39
+ cardId: input.params.cardId,
40
+ playedBy: input.playerId,
41
+ }),
42
+ ops.patchPublicState({ selectedSpaceId: input.params.selectedSpaceId }),
43
+ ),
44
+ );
45
+ },
46
+ });
47
+ ```
48
+
49
+ ## How it differs from defineInteraction
50
+
51
+ `defineCardAction` supplies the card-specific contract:
52
+
53
+ | Field | Meaning |
54
+ | --- | --- |
55
+ | `cardType` | Manifest card type that can trigger this action. |
56
+ | `playFrom` | Manifest player zone id the card must be playable from. |
57
+ | `inputs` | Additional params besides the selected card. |
58
+ | `step` / `steps` | Optional phase-step gates, typed from `state.phase.step` when your phase state schema declares a string literal union. |
59
+ | `rules` | Card-specific availability and validation, often shared across a family of cards. |
60
+ | `cost` / `paramsSchema` / `commit` | Same interaction metadata as `defineInteraction`, when the card action needs affordability, cross-field params, or manual draft control. |
61
+ | `reduce` | Receives `input.params.cardId` automatically. |
62
+
63
+ The generated interaction contract treats card actions as card-anchored
64
+ interactions. `Zone` plus `Interaction.CardInput` can show a playable card and
65
+ collect additional inputs without each workspace inventing local card-action
66
+ plumbing.
67
+
68
+ Card actions are always card-anchored interactions. Use `defineInteraction` for
69
+ non-card commands and group them visually in React.
70
+
71
+ ## Zone-backed rendering
72
+
73
+ Declare the hand zone once in the phase:
74
+
75
+ ```ts
76
+ zones: [zones.actionHand],
77
+ cardActions: {
78
+ playBoost,
79
+ playDrawCards,
80
+ playBuildPath,
81
+ },
82
+ ```
83
+
84
+ The reducer projection is keyed by the manifest zone id, such as `action-hand`.
85
+ Import `zones` and `cardTypes` from the generated manifest contract so authoring
86
+ code uses typed handles instead of raw string ids. Cards come from the listed
87
+ manifest player zone; playable descriptors are derived from `cardActions` whose
88
+ `playFrom` matches that zone.
89
+
90
+ ## Validation
91
+
92
+ Target validation still runs before authored rules. Use `rules` for
93
+ card-specific business checks: once per turn, bought this turn, enough
94
+ resources, correct phase step, or no pending trade.
95
+
96
+ Move the card in `reduce`. `defineCardAction` makes the card id available; it does not decide whether the card should be discarded, trashed, returned, tucked, or left in play.
97
+
98
+ ## When to use defineInteraction instead
99
+
100
+ Use `defineInteraction` when:
101
+
102
+ - the action is not tied to a card instance
103
+ - the player chooses from a board target, form, or panel button first
104
+ - a prompt asks for a response
105
+ - the card is only one optional input among several unrelated choices
106
+
107
+ Use `defineCardAction` when the card is the object being played.