@dreamboard-games/cli 0.1.30-alpha.1 → 0.1.30-alpha.3

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 (163) hide show
  1. package/README.md +179 -22
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +30 -30
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -0
  4. package/dist/agent-verifier/{chunk-JH22JNYD.mjs → chunk-3UKQVWLV.mjs} +82 -19
  5. package/dist/agent-verifier/chunk-3UKQVWLV.mjs.map +1 -0
  6. package/dist/agent-verifier/{chunk-4WD3YU2E.mjs → chunk-776W3UGV.mjs} +4 -3
  7. package/dist/agent-verifier/chunk-776W3UGV.mjs.map +1 -0
  8. package/dist/agent-verifier/{chunk-CJEEA6NJ.mjs → chunk-7WWGFAAU.mjs} +9 -10
  9. package/dist/agent-verifier/chunk-7WWGFAAU.mjs.map +1 -0
  10. package/dist/agent-verifier/{chunk-2SZHMP6F.mjs → chunk-A64ZZUZV.mjs} +6 -9
  11. package/dist/agent-verifier/chunk-A64ZZUZV.mjs.map +1 -0
  12. package/dist/agent-verifier/{chunk-6A5HRJMQ.mjs → chunk-E7SSWJXJ.mjs} +62 -99
  13. package/dist/agent-verifier/chunk-E7SSWJXJ.mjs.map +1 -0
  14. package/dist/agent-verifier/{chunk-2GBBP27W.mjs → chunk-F2DIOJJZ.mjs} +1 -0
  15. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +1 -0
  16. package/dist/agent-verifier/{chunk-CFU5EWIC.mjs → chunk-G42BGGG2.mjs} +7 -6
  17. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +1 -0
  18. package/dist/agent-verifier/{chunk-SHUMAVAP.mjs → chunk-H76MT5UR.mjs} +7 -9
  19. package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +1 -0
  20. package/dist/agent-verifier/{chunk-7E65UQLY.mjs → chunk-HGMUAL33.mjs} +3 -2
  21. package/dist/agent-verifier/chunk-HGMUAL33.mjs.map +1 -0
  22. package/dist/agent-verifier/{chunk-LM3OZLZG.mjs → chunk-IAYRNVUC.mjs} +1 -0
  23. package/dist/agent-verifier/chunk-IAYRNVUC.mjs.map +1 -0
  24. package/dist/agent-verifier/{chunk-VYJTHSYR.mjs → chunk-JGT4P4UD.mjs} +2 -1
  25. package/dist/agent-verifier/chunk-JGT4P4UD.mjs.map +1 -0
  26. package/dist/agent-verifier/{chunk-CEDUHGNH.mjs → chunk-LUZ7KE6H.mjs} +8 -3
  27. package/dist/agent-verifier/chunk-LUZ7KE6H.mjs.map +1 -0
  28. package/dist/agent-verifier/{chunk-2E5P5NWG.mjs → chunk-NAK77WXW.mjs} +58 -126
  29. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +1 -0
  30. package/dist/agent-verifier/{chunk-SYPLYRGB.mjs → chunk-O4YCPU7C.mjs} +116 -15
  31. package/dist/agent-verifier/chunk-O4YCPU7C.mjs.map +1 -0
  32. package/dist/agent-verifier/{chunk-BVVNBJM4.mjs → chunk-S34FRJHS.mjs} +2 -1
  33. package/dist/agent-verifier/chunk-S34FRJHS.mjs.map +1 -0
  34. package/dist/agent-verifier/{chunk-HJFQDSTU.mjs → chunk-SH5JKYOB.mjs} +6 -5
  35. package/dist/agent-verifier/chunk-SH5JKYOB.mjs.map +1 -0
  36. package/dist/agent-verifier/chunk-SKI2ESE5.mjs +44 -0
  37. package/dist/agent-verifier/{chunk-MINCYHXN.mjs → chunk-TAEQKBJB.mjs} +1 -0
  38. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +1 -0
  39. package/dist/agent-verifier/{chunk-CEQ2VJWN.mjs → chunk-UIOLGH4A.mjs} +2 -1
  40. package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +1 -0
  41. package/dist/agent-verifier/chunk-UIZNWRM6.mjs +2432 -0
  42. package/dist/agent-verifier/chunk-UIZNWRM6.mjs.map +1 -0
  43. package/dist/agent-verifier/{chunk-2QMNAVV4.mjs → chunk-VS573ERH.mjs} +2 -1
  44. package/dist/agent-verifier/chunk-VS573ERH.mjs.map +1 -0
  45. package/dist/agent-verifier/{chunk-EOQIV6PS.mjs → chunk-W3N3QJ4V.mjs} +75 -100
  46. package/dist/agent-verifier/chunk-W3N3QJ4V.mjs.map +1 -0
  47. package/dist/agent-verifier/{chunk-EIQWDQWJ.mjs → chunk-XGWCY624.mjs} +11 -12
  48. package/dist/agent-verifier/chunk-XGWCY624.mjs.map +1 -0
  49. package/dist/agent-verifier/{chunk-7653FPGJ.mjs → chunk-XQXDOBYB.mjs} +3 -2
  50. package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +1 -0
  51. package/dist/agent-verifier/{chunk-MRCUP5SW.mjs → chunk-YE7UAO3T.mjs} +1 -0
  52. package/dist/agent-verifier/chunk-YE7UAO3T.mjs.map +1 -0
  53. package/dist/agent-verifier/{chunk-RBDDIIPM.mjs → chunk-ZEELHSY3.mjs} +1 -0
  54. package/dist/agent-verifier/chunk-ZEELHSY3.mjs.map +1 -0
  55. package/dist/agent-verifier/{compile-5QSPIOUT.mjs → compile-TEQVA46V.mjs} +24 -25
  56. package/dist/agent-verifier/compile-TEQVA46V.mjs.map +1 -0
  57. package/dist/agent-verifier/{global-config-WX3ZZIVU.mjs → global-config-Y2NTSK4R.mjs} +6 -5
  58. package/dist/{keychain-backend-JHTXAKWC.js → agent-verifier/keychain-backend-SPQWGKZN.mjs} +2 -2
  59. package/dist/agent-verifier/keychain-backend-SPQWGKZN.mjs.map +1 -0
  60. package/dist/agent-verifier/{local-files-MTPLP62S.mjs → local-files-JFOQQZDL.mjs} +10 -11
  61. package/dist/agent-verifier/local-files-JFOQQZDL.mjs.map +1 -0
  62. package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs +10 -0
  63. package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs.map +1 -0
  64. package/dist/agent-verifier/{materialize-workspace-FKALAE2T.mjs → materialize-workspace-ZAVGQQSF.mjs} +17 -18
  65. package/dist/agent-verifier/materialize-workspace-ZAVGQQSF.mjs.map +1 -0
  66. package/dist/agent-verifier/{project-state-7GR6BQTQ.mjs → project-state-K576C2TE.mjs} +3 -2
  67. package/dist/agent-verifier/project-state-K576C2TE.mjs.map +1 -0
  68. package/dist/{prompt-GMZABCJC.js → agent-verifier/prompt-MJRJMOGQ.mjs} +2 -2
  69. package/dist/agent-verifier/prompt-MJRJMOGQ.mjs.map +1 -0
  70. package/dist/agent-verifier/{reducer-bundle-preflight-C73LEXI2.mjs → reducer-bundle-preflight-LXNJUBKL.mjs} +6 -9
  71. package/dist/agent-verifier/reducer-bundle-preflight-LXNJUBKL.mjs.map +1 -0
  72. package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs +11 -0
  73. package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs.map +1 -0
  74. package/dist/agent-verifier/{reducer-native-test-harness-GMWBUISX.mjs → reducer-native-test-harness-CHX5MBL5.mjs} +14 -17
  75. package/dist/agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs.map +1 -0
  76. package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs +27 -0
  77. package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs.map +1 -0
  78. package/dist/agent-verifier/{sync-3DUQH32H.mjs → sync-THAI546U.mjs} +31 -37
  79. package/dist/agent-verifier/sync-THAI546U.mjs.map +1 -0
  80. package/dist/agent-verifier/{test-P4U5INTD.mjs → test-AFAQFKOB.mjs} +28 -31
  81. package/dist/agent-verifier/test-AFAQFKOB.mjs.map +1 -0
  82. package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs +10 -0
  83. package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs.map +1 -0
  84. package/dist/agent-verifier/{workspace-dependencies-HZ6VVS4G.mjs → workspace-dependencies-NOOQBK6I.mjs} +5 -4
  85. package/dist/agent-verifier/workspace-dependencies-NOOQBK6I.mjs.map +1 -0
  86. package/dist/{chunk-C6UAT6EH.js → chunk-N7XPNNUI.js} +9 -12
  87. package/dist/chunk-N7XPNNUI.js.map +1 -0
  88. package/dist/chunk-SEGVTWSK.js +44 -0
  89. package/dist/chunk-SEGVTWSK.js.map +1 -0
  90. package/dist/{chunk-RS7UXJZV.js → chunk-TAQKH67O.js} +21300 -35881
  91. package/dist/chunk-TAQKH67O.js.map +1 -0
  92. package/dist/{global-config-AGFBDFYD.js → global-config-S4ZIPECE.js} +3 -3
  93. package/dist/global-config-S4ZIPECE.js.map +1 -0
  94. package/dist/index.js +415 -37
  95. package/dist/index.js.map +1 -1
  96. package/dist/internal.js +3 -4
  97. package/dist/{agent-verifier/keychain-backend-TNOPQV3Z.mjs → keychain-backend-HDF4TZDL.js} +2 -1
  98. package/dist/{agent-verifier/prompt-3BAINGAQ.mjs → prompt-NDV3AE5L.js} +2 -1
  99. package/package.json +8 -7
  100. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  101. package/skills/dreamboard/references/cli.md +104 -0
  102. package/skills/dreamboard/references/game-interface.md +548 -0
  103. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  104. package/skills/dreamboard/references/quickstart.md +66 -0
  105. package/skills/dreamboard/references/reducer.md +864 -0
  106. package/skills/dreamboard/references/rule-authoring.md +147 -0
  107. package/skills/dreamboard/references/testing.md +249 -0
  108. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  109. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  110. package/dist/agent-verifier/chunk-6UUJEYDV.mjs +0 -213
  111. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  112. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  113. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  114. package/dist/agent-verifier/chunk-XYDL7GY6.mjs +0 -10
  115. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  116. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  117. package/dist/agent-verifier/static-scaffold-AJMZZQWS.mjs +0 -28
  118. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  119. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  120. package/dist/chunk-2H7UOFLK.js +0 -11
  121. package/dist/chunk-7FOO4AJI.js +0 -50
  122. package/dist/chunk-7FOO4AJI.js.map +0 -1
  123. package/dist/chunk-C6UAT6EH.js.map +0 -1
  124. package/dist/chunk-RS7UXJZV.js.map +0 -1
  125. package/dist/internal.d.ts +0 -311
  126. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  127. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  128. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  129. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  130. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  131. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  132. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  133. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  134. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  135. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  136. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  137. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  138. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  139. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  140. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  141. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  142. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  143. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  144. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  145. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  146. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  147. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  148. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  149. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  150. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  151. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  152. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  153. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  154. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  155. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  156. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  157. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  158. package/dist/testing-KLSV6CPJ.js +0 -674
  159. package/dist/testing-KLSV6CPJ.js.map +0 -1
  160. /package/dist/{chunk-2H7UOFLK.js.map → agent-verifier/chunk-SKI2ESE5.mjs.map} +0 -0
  161. /package/dist/{global-config-AGFBDFYD.js.map → agent-verifier/global-config-Y2NTSK4R.mjs.map} +0 -0
  162. /package/dist/{keychain-backend-JHTXAKWC.js.map → keychain-backend-HDF4TZDL.js.map} +0 -0
  163. /package/dist/{prompt-GMZABCJC.js.map → prompt-NDV3AE5L.js.map} +0 -0
@@ -0,0 +1,864 @@
1
+ <!-- Generated by apps/dreamboard-cli/scripts/sync-skill-docs.ts. -->
2
+ <!-- Source: docs/reference/reducer.mdx -->
3
+
4
+ # Reducer
5
+
6
+ Reference for authoring Dreamboard reducer-native games with @dreamboard/app-sdk/reducer.
7
+
8
+ `@dreamboard/app-sdk/reducer` is Dreamboard's reducer-native authoring surface that enforces game logic.
9
+ Use it to define state schemas, phases, actions, prompts, views and setup profiles.
10
+
11
+ ## Package boundary
12
+
13
+ Use the reducer framework for:
14
+
15
+ - manifest-backed reducer state schemas
16
+ - phase flow and player action handling
17
+ - prompts, continuations, and reducer-owned windows
18
+ - player-facing view projection
19
+ - setup bootstrap steps
20
+
21
+ ## Purity
22
+
23
+ Reducer is pure-function that takes game state as input and produces a deterministic output.
24
+
25
+ `enter(...)`, `validate(...)`, `reduce(...)`, prompt continuations, and system
26
+ handlers should derive results only from input plus reducer state.
27
+ Do not call ambient sources of nondeterminism such as:
28
+
29
+ - `Math.random()`
30
+ - `Date.now()`
31
+ - network I/O
32
+ - filesystem I/O
33
+ - mutable process-global state
34
+
35
+ When gameplay needs side effects, queue them through `effects` and let the
36
+ runtime execute them. This keeps reducer behavior replayable, testable, and
37
+ compatible with seeded runtime state.
38
+
39
+ `effects` are runtime effect descriptors, not direct imperative APIs. Reducer
40
+ callbacks emit them, and the reducer bundle/runtime interprets them after the
41
+ pure reducer step completes.
42
+
43
+ ## Import surface
44
+
45
+ Import reducer helpers from `@dreamboard/app-sdk/reducer`.
46
+
47
+ ```ts
48
+ import {
49
+ createReducerBundle,
50
+ defineAction,
51
+ defineChoicePrompt,
52
+ defineContinuation,
53
+ defineGame,
54
+ defineGameContract,
55
+ definePhase,
56
+ definePrompt,
57
+ definePromptContinuation,
58
+ defineSetupProfilesFor,
59
+ defineView,
60
+ defineWindowContinuation,
61
+ getPlayerZoneCards,
62
+ getSharedZoneCards,
63
+ moveCardFromPlayerZoneToSharedZone,
64
+ setActivePlayers,
65
+ setPhaseState,
66
+ } from "@dreamboard/app-sdk/reducer";
67
+ ```
68
+
69
+ `@dreamboard/app-sdk` currently re-exports the same surface, but
70
+ `@dreamboard/app-sdk/reducer` is the explicit reducer import path.
71
+
72
+ ## `defineGameContract(...)`
73
+
74
+ `defineGameContract(...)` binds the generated manifest contract to the reducer's
75
+ shared, per-player, and hidden state schemas.
76
+
77
+ | Field | Required | Notes |
78
+ | --- | --- | --- |
79
+ | `manifest` | Yes | Generated manifest contract from `shared/manifest-contract` |
80
+ | `state.public` | Yes | Shared state visible to every player |
81
+ | `state.private` | Yes | Per-player server-only state |
82
+ | `state.hidden` | Yes | Reducer-only state that never reaches clients |
83
+ | `state.initial.public` | No | Lazy initializer for `publicState` |
84
+ | `state.initial.private` | No | Lazy initializer for one player's private state |
85
+ | `state.initial.hidden` | No | Lazy initializer for `hiddenState` |
86
+
87
+ Initializers receive:
88
+
89
+ - `manifest`
90
+ - `table`
91
+ - `playerIds`
92
+ - `playerId` for `state.initial.private`
93
+ - `rngSeed`
94
+ - `setup`
95
+
96
+ ```ts
97
+ import { z } from "zod";
98
+ import { defineGameContract } from "@dreamboard/app-sdk/reducer";
99
+ import { manifestContract, ids } from "../shared/manifest-contract";
100
+
101
+ export const gameContract = defineGameContract({
102
+ manifest: manifestContract,
103
+ state: {
104
+ public: z.object({
105
+ currentJudgeId: ids.playerId,
106
+ winnerPlayerId: ids.playerId.nullable(),
107
+ }),
108
+ private: z.object({
109
+ secretNotes: z.array(z.string()),
110
+ }),
111
+ hidden: z.object({
112
+ seededRoundId: z.string(),
113
+ }),
114
+ initial: {
115
+ public: ({ playerIds }) => ({
116
+ currentJudgeId: playerIds[0],
117
+ winnerPlayerId: null,
118
+ }),
119
+ private: () => ({
120
+ secretNotes: [],
121
+ }),
122
+ hidden: ({ rngSeed }) => ({
123
+ seededRoundId: `round-${rngSeed ?? 0}`,
124
+ }),
125
+ },
126
+ },
127
+ });
128
+ ```
129
+
130
+ ## Generated `shared/manifest-contract`
131
+
132
+ The generated `shared/manifest-contract` module exports more than the
133
+ `manifestContract` object you pass into `defineGameContract(...)`.
134
+
135
+ | Export | Use |
136
+ | --- | --- |
137
+ | `manifestContract` | Reducer-facing manifest contract passed into `defineGameContract(...)` |
138
+ | `ids` | Zod literal schemas such as `ids.playerId`, `ids.boardId`, and `ids.spaceId` |
139
+ | `literals` | Const arrays and lookup records for authored IDs and categories |
140
+ | `defaults` | Lazy helpers for manifest-backed empty table state |
141
+ | `schemas` | Generated `table` and `runtime` Zod schemas |
142
+ | `createGameStateSchema(...)` | Builds a full reducer runtime schema around your phase and state schemas |
143
+ | `boardHelpers` | Literal lookup tables for board IDs, layouts, spaces, containers, edges, and vertices |
144
+
145
+ ### `defaults`
146
+
147
+ Use `defaults` when you need manifest-shaped empty state outside the reducer
148
+ bundle, such as test setup or custom initialization.
149
+
150
+ | Helper | Returns |
151
+ | --- | --- |
152
+ | `defaults.zones(playerIds?)` | Empty shared/per-player zone state, visibility, and allowed card-set lookup |
153
+ | `defaults.decks()` | Empty shared deck contents |
154
+ | `defaults.hands(playerIds?)` | Empty per-player hand contents |
155
+ | `defaults.handVisibility()` | Zone visibility map for player hands |
156
+ | `defaults.ownerOfCard()` | Default card ownership map |
157
+ | `defaults.visibility()` | Default card visibility map |
158
+ | `defaults.resources(playerIds?)` | Zeroed per-player resource balances |
159
+
160
+ Pass `playerIds` when you want to seed only a subset of manifest players.
161
+ Omit it to use all authored player IDs.
162
+
163
+ ```ts
164
+ import {
165
+ boardHelpers,
166
+ defaults,
167
+ type BoardFieldsByBoardId,
168
+ type DieFieldsByTypeId,
169
+ type PieceFieldsByTypeId,
170
+ } from "../shared/manifest-contract";
171
+
172
+ const initialZones = defaults.zones(["player-1", "player-2"]);
173
+ const initialHands = defaults.hands(["player-1", "player-2"]);
174
+ const initialResources = defaults.resources(["player-1", "player-2"]);
175
+
176
+ type MainBoardFields = BoardFieldsByBoardId["main-board"];
177
+ type WorkerFields = PieceFieldsByTypeId["worker"];
178
+ type D6Fields = DieFieldsByTypeId["d6"];
179
+
180
+ const harborEdgeIds = boardHelpers.edgeIdsByTypeId.harbor ?? [];
181
+ ```
182
+
183
+ ### Generated field-map types
184
+
185
+ Generated manifest contracts also export typed field maps so reducer code can
186
+ index manifest-authored fields without falling back to `Record<string, unknown>`.
187
+
188
+ | Type | Maps |
189
+ | --- | --- |
190
+ | `BoardFieldsByBoardId` | Runtime board ID -> board `fields` shape |
191
+ | `BoardSpaceFieldsByBoardId` | Runtime board ID -> per-space `fields` shape |
192
+ | `BoardRelationFieldsByBoardId` | Runtime board ID -> per-relation `fields` shape |
193
+ | `BoardContainerFieldsByBoardId` | Runtime board ID -> per-container `fields` shape |
194
+ | `HexEdgeFieldsByBoardId` | Runtime hex board ID -> per-edge `fields` shape |
195
+ | `HexVertexFieldsByBoardId` | Runtime hex board ID -> per-vertex `fields` shape |
196
+ | `PieceFieldsByTypeId` | Piece type ID -> piece `properties` shape |
197
+ | `DieFieldsByTypeId` | Die type ID -> die `properties` shape |
198
+
199
+ ### `boardHelpers`
200
+
201
+ `boardHelpers` exposes literal lookup tables that pair well with reducer table
202
+ helpers.
203
+
204
+ | Helper | Notes |
205
+ | --- | --- |
206
+ | `boardIdsByBaseId` | Expands authored base board IDs into runtime board IDs |
207
+ | `boardLayoutById` | Runtime board ID -> `generic` or `hex` |
208
+ | `boardIdsByTypeId` | Reducer-facing board `typeId` -> runtime board IDs |
209
+ | `spaceIdsByBoardId` | Runtime board ID -> all space IDs on that board |
210
+ | `spaceIdsByTypeId` | Space `typeId` -> matching space IDs |
211
+ | `containerIdsByBoardId` | Runtime board ID -> board container IDs |
212
+ | `relationTypeIdsByBoardId` | Runtime board ID -> relation `typeId`s |
213
+ | `edgeIdsByTypeId` | Hex edge `typeId` -> edge IDs |
214
+ | `vertexIdsByTypeId` | Hex vertex `typeId` -> vertex IDs |
215
+
216
+ ### Board-aware component locations
217
+
218
+ `state.table.componentLocations` can place components in these board-aware
219
+ runtime locations:
220
+
221
+ | `type` | Extra fields |
222
+ | --- | --- |
223
+ | `OnSpace` | `boardId`, `spaceId`, optional `position` |
224
+ | `InContainer` | `boardId`, `containerId`, optional `position` |
225
+ | `OnEdge` | `boardId`, `edgeId`, optional `position` |
226
+ | `OnVertex` | `boardId`, `vertexId`, optional `position` |
227
+ | `InSlot` | `hostComponentId`, `slotId`, optional `position` |
228
+
229
+ The table helpers in this package update those locations for you:
230
+ `moveComponentToSpace(...)`, `moveComponentToContainer(...)`,
231
+ `moveComponentToEdge(...)`, and `moveComponentToVertex(...)`.
232
+
233
+ ## `defineGame(...)`
234
+
235
+ `defineGame(...)` assembles the reducer definition that `createReducerBundle(...)`
236
+ executes.
237
+
238
+ | Field | Required | Notes |
239
+ | --- | --- | --- |
240
+ | `contract` | Yes | Output from `defineGameContract(...)` |
241
+ | `phases` | Yes | Phase registry; object keys are the phase names |
242
+ | `initialPhase` | No | Default starting phase unless a setup profile overrides it |
243
+ | `setupProfiles` | No | Typed setup-profile overrides and bootstrap steps |
244
+ | `views` | No | Named player-facing view projections |
245
+ | `root.system` | No | Root-level system handlers |
246
+ | `root.selectors` | No | Root-level derived selectors |
247
+
248
+ If you omit `initialPhase`, the first registered phase is used.
249
+
250
+ ```ts
251
+ import { defineGame } from "@dreamboard/app-sdk/reducer";
252
+ import { gameContract } from "./game-contract";
253
+ import { phases } from "./phases";
254
+ import { playerView } from "./player-view";
255
+ import { setupProfiles } from "./setup-profiles";
256
+
257
+ export default defineGame({
258
+ contract: gameContract,
259
+ initialPhase: "dealCards",
260
+ setupProfiles,
261
+ phases,
262
+ views: {
263
+ player: playerView,
264
+ },
265
+ });
266
+ ```
267
+
268
+ ## `defineSetupProfilesFor(...)` and `defineSetupProfiles(...)`
269
+
270
+ Use setup profiles when the selected setup should change the initial phase,
271
+ bootstrap authored inventory, or both.
272
+
273
+ `defineSetupProfilesFor(manifestContract)<SetupProfileId>()(...)` is the
274
+ manifest-aware helper for app code. `defineSetupProfiles<SetupProfileId>()(...)`
275
+ is the generic helper when you already control the setup-profile ID union.
276
+
277
+ ### `SetupProfileDefinition`
278
+
279
+ | Field | Required | Notes |
280
+ | --- | --- | --- |
281
+ | `initialPhase` | No | Overrides `defineGame(...).initialPhase` for that setup |
282
+ | `bootstrap` | No | Ordered setup bootstrap steps applied before play starts |
283
+
284
+ ### `SetupBootstrapStep`
285
+
286
+ | Variant | Required fields | Notes |
287
+ | --- | --- | --- |
288
+ | `type: "shuffle"` | `container` | Shuffles a shared zone, player zone, or board container |
289
+ | `type: "move"` | `from`, `to` | Moves explicit or counted components between containers or onto board spaces |
290
+ | `type: "deal"` | `from`, `to`, `count` | Deals from a shared source into a per-player destination template |
291
+
292
+ For `move` steps:
293
+
294
+ - `from` must be a container reference
295
+ - `to` may be a container reference or a board-space reference
296
+ - use `count` for top-of-container movement
297
+ - use `componentIds` for exact authored components
298
+
299
+ For `deal` steps:
300
+
301
+ - `from` must be a shared zone or shared board container
302
+ - `to` must be a player zone or player board container template
303
+ - `playerIds` is optional; omit it to target every player
304
+
305
+ ```ts
306
+ import {
307
+ defineSetupProfilesFor,
308
+ type SetupProfileDefinition,
309
+ } from "@dreamboard/app-sdk/reducer";
310
+ import {
311
+ manifestContract,
312
+ type SetupProfileId,
313
+ } from "../shared/manifest-contract";
314
+
315
+ export const setupProfiles = defineSetupProfilesFor(
316
+ manifestContract,
317
+ )<SetupProfileId>()({
318
+ "draft-setup": {
319
+ initialPhase: "draft",
320
+ bootstrap: [
321
+ {
322
+ type: "shuffle",
323
+ container: {
324
+ type: "sharedZone",
325
+ zoneId: "draw-deck",
326
+ },
327
+ },
328
+ {
329
+ type: "deal",
330
+ from: {
331
+ type: "sharedZone",
332
+ zoneId: "draw-deck",
333
+ },
334
+ to: {
335
+ type: "playerZone",
336
+ zoneId: "main-hand",
337
+ },
338
+ count: 5,
339
+ },
340
+ {
341
+ type: "move",
342
+ from: {
343
+ type: "sharedBoardContainer",
344
+ boardId: "market-board",
345
+ containerId: "offer-row",
346
+ },
347
+ to: {
348
+ type: "sharedBoardSpace",
349
+ boardId: "market-board",
350
+ spaceId: "slot-a",
351
+ },
352
+ count: 1,
353
+ },
354
+ ],
355
+ } satisfies SetupProfileDefinition<"draft", typeof manifestContract>,
356
+ });
357
+ ```
358
+
359
+ ## `definePhase(...)`
360
+
361
+ `definePhase(...)` declares one reducer phase.
362
+
363
+ | Field | Required | Notes |
364
+ | --- | --- | --- |
365
+ | `kind` | No | `auto` or `player` |
366
+ | `state` | Yes | Zod schema for phase-local state |
367
+ | `initialState` | No | Initializes `state.phases[phaseName]` |
368
+ | `enter` | No | Runs on initialization and on transitions into the phase |
369
+ | `actions` | No | Player actions available in this phase |
370
+ | `prompts` | No | Prompt registry for this phase |
371
+ | `continuations` | No | Continuation registry for prompt or window resumes |
372
+ | `windows` | No | Window registry; `id` defaults to the object key |
373
+ | `system` | No | Handlers for `effects.dispatchSystem(...)` and scheduled inputs |
374
+ | `selectors` | No | Named derived selectors |
375
+
376
+ `initialState(...)` receives `manifest`, `state`, `playerIds`, and `setup`.
377
+
378
+ `enter(...)` receives the same reducer callback helpers as actions, plus
379
+ `event`, which is either `initialize` or `transition`.
380
+
381
+ Use `kind: "auto"` for phases that should resolve immediately from reducer
382
+ logic without waiting for a player action. Use `kind: "player"` for phases that
383
+ wait for one or more active players to submit actions.
384
+
385
+ ```ts
386
+ import { z } from "zod";
387
+ import { definePhase, setActivePlayers } from "@dreamboard/app-sdk/reducer";
388
+ import type { GameContract } from "../game-contract";
389
+
390
+ export const placeThing = definePhase<GameContract>()({
391
+ kind: "player",
392
+ state: z.object({}),
393
+ initialState: () => ({}),
394
+ enter({ state, accept }) {
395
+ return accept(setActivePlayers(state, [state.publicState.currentJudgeId]));
396
+ },
397
+ });
398
+ ```
399
+
400
+ Continuation IDs and window IDs default to their registry keys when you omit
401
+ `id`.
402
+
403
+ ## `defineAction(...)`
404
+
405
+ `defineAction(...)` declares one typed player action.
406
+
407
+ | Field | Required | Notes |
408
+ | --- | --- | --- |
409
+ | `params` | Yes | Zod schema for `input.params` |
410
+ | `displayName` | No | Metadata label |
411
+ | `description` | No | Metadata description |
412
+ | `errorCodes` | No | Declared rejection codes |
413
+ | `available` | No | UI/runtime availability filter for `getAvailableActions(...)` |
414
+ | `validate` | No | Returns `null` or `{ errorCode, message }` |
415
+ | `reduce` | Yes | Returns `accept(...)`, `reject(...)`, or a `{ state, effects }` object |
416
+
417
+ Keep hard legality checks in `validate(...)` or `reduce(...)`. `available(...)`
418
+ only filters the surfaced available-action list.
419
+
420
+ ```ts
421
+ import { z } from "zod";
422
+ import { defineAction } from "@dreamboard/app-sdk/reducer";
423
+ import type { GameContract } from "../game-contract";
424
+
425
+ const placeThing = defineAction<GameContract>()({
426
+ params: z.object({
427
+ cardId: z.string(),
428
+ ringId: z.string(),
429
+ }),
430
+ validate({ state, input }) {
431
+ if (!state.flow.activePlayers.includes(input.playerId)) {
432
+ return {
433
+ errorCode: "NOT_YOUR_TURN",
434
+ message: "It is not your turn.",
435
+ };
436
+ }
437
+ return null;
438
+ },
439
+ reduce({ state, input, accept, effects }) {
440
+ return accept(state, [effects.transition("judgeRings")]);
441
+ },
442
+ });
443
+ ```
444
+
445
+ ## `definePrompt(...)` and `defineChoicePrompt(...)`
446
+
447
+ Use prompts when reducer flow must pause and resume later with a typed response.
448
+
449
+ ### Prompt vs window
450
+
451
+ Use a prompt when you want one player to answer a reducer-owned request with a
452
+ typed response payload.
453
+
454
+ Use a window when you want the runtime to open a richer interactive session
455
+ that can target one or more players, accept multiple action types, and close
456
+ under a runtime close policy.
457
+
458
+ Prompts are narrower and response-focused. Windows are broader and
459
+ session-focused.
460
+
461
+ ### `definePrompt(...)`
462
+
463
+ | Field | Required | Notes |
464
+ | --- | --- | --- |
465
+ | `id` | Yes | Prompt type ID |
466
+ | `title` | No | Default title |
467
+ | `responseSchema` | Yes | Zod schema for the response payload |
468
+
469
+ ```ts
470
+ const judgeNotePrompt = definePrompt<GameContract>()({
471
+ id: "judge-note",
472
+ title: "Explain the ruling",
473
+ responseSchema: z.object({
474
+ note: z.string().min(1),
475
+ }),
476
+ });
477
+ ```
478
+
479
+ ### `defineChoicePrompt(...)`
480
+
481
+ | Field | Required | Notes |
482
+ | --- | --- | --- |
483
+ | `id` | Yes | Prompt type ID |
484
+ | `title` | No | Default title |
485
+ | `options` | Yes | Non-empty option list; response type is inferred from option IDs |
486
+
487
+ ```ts
488
+ const judgePlacementPrompt = defineChoicePrompt<GameContract>()({
489
+ id: "judge-placement",
490
+ title: "Where should this thing actually go?",
491
+ options: [
492
+ { id: "ring-1", label: "Ring 1" },
493
+ { id: "ring-2", label: "Ring 2" },
494
+ { id: "discard", label: "Not in any ring" },
495
+ ] as const,
496
+ });
497
+ ```
498
+
499
+ When you open a choice prompt, any runtime `options` override must still use the
500
+ declared option IDs.
501
+
502
+ ## `definePromptContinuation(...)`, `defineWindowContinuation(...)`, and `defineContinuation(...)`
503
+
504
+ Continuations resume typed reducer logic after a prompt response or window
505
+ action.
506
+
507
+ | Helper | Resume source | Typed input |
508
+ | --- | --- | --- |
509
+ | `definePromptContinuation(...)` | One declared prompt | `input.promptId` and prompt-typed `input.response` |
510
+ | `defineWindowContinuation(...)` | Window action | `input.windowId`, `input.actionType`, and typed `input.response` |
511
+ | `defineContinuation(...)` | Shared prompt, window, or runtime-effect flow | Union on `input.source` |
512
+
513
+ ### `definePromptContinuation(...)`
514
+
515
+ ```ts
516
+ const resolvePlacement = definePromptContinuation<GameContract>()({
517
+ prompt: judgePlacementPrompt,
518
+ data: z.object({
519
+ pendingCardId: z.string(),
520
+ }),
521
+ reduce({ state, input, accept, effects }) {
522
+ return accept(state, [effects.closePrompt(input.promptId)]);
523
+ },
524
+ });
525
+ ```
526
+
527
+ ### `defineWindowContinuation(...)`
528
+
529
+ ```ts
530
+ const reviewWindow = {
531
+ id: "review-window",
532
+ } as const;
533
+
534
+ const resolveReview = defineWindowContinuation<GameContract>()({
535
+ data: z.object({
536
+ pendingCardId: z.string(),
537
+ }),
538
+ response: z.object({
539
+ actionType: z.literal("confirm"),
540
+ params: z.object({
541
+ approved: z.boolean(),
542
+ }),
543
+ windowId: z.literal("review-window"),
544
+ }),
545
+ reduce({ state, input, accept }) {
546
+ return accept(state);
547
+ },
548
+ });
549
+ ```
550
+
551
+ ### `defineContinuation(...)`
552
+
553
+ Use the shared helper when one continuation should accept prompt resumes,
554
+ window resumes, or runtime-effect resumes and branch on `input.source`.
555
+
556
+ ```ts
557
+ const resolveSharedPlacement = defineContinuation<GameContract>()({
558
+ data: z.object({
559
+ pendingCardId: z.string(),
560
+ }),
561
+ response: judgePlacementPrompt.responseSchema,
562
+ reduce({ state, input, accept }) {
563
+ if (input.source !== "prompt") {
564
+ return accept(state);
565
+ }
566
+ return accept(state);
567
+ },
568
+ });
569
+ ```
570
+
571
+ Runtime-owned effects such as `effects.randomInt(...)` resume shared
572
+ continuations with `input.source === "shared"`.
573
+
574
+ ## `defineView(...)`
575
+
576
+ Views are typed, player-facing projections of reducer state.
577
+
578
+ | Field | Required | Notes |
579
+ | --- | --- | --- |
580
+ | `schema` | Yes | Zod schema for the projected payload |
581
+ | `project` | Yes | Maps reducer state to one player's view |
582
+
583
+ `project(...)` receives `state`, `playerId`, `runtime`, `manifest`, and the
584
+ same callback helpers as phases and actions.
585
+
586
+ ```ts
587
+ import { z } from "zod";
588
+ import { defineView } from "@dreamboard/app-sdk/reducer";
589
+ import type { GameContract } from "./game-contract";
590
+
591
+ export const playerView = defineView<GameContract>()({
592
+ schema: z.object({
593
+ turn: z.number(),
594
+ isJudge: z.boolean(),
595
+ }),
596
+ project({ state, playerId }) {
597
+ return {
598
+ turn: state.flow.turn,
599
+ isJudge: playerId === state.publicState.currentJudgeId,
600
+ };
601
+ },
602
+ });
603
+ ```
604
+
605
+ ## Reducer callback helpers
606
+
607
+ Reducer callbacks such as `enter(...)`, `validate(...)`, `reduce(...)`,
608
+ continuation reducers, and view projectors receive shared runtime helpers.
609
+
610
+ | Helper | Notes |
611
+ | --- | --- |
612
+ | `accept(state, effects?)` | Successful reducer result |
613
+ | `reject(errorCode, message?)` | Immediate rejection |
614
+ | `manifest` | Generated manifest contract |
615
+ | `setup` | Selected setup profile plus resolved option values |
616
+ | `currentPhase` | Current phase name |
617
+ | `playerOrder` | Full turn order |
618
+ | `activePlayers` | Current active player IDs |
619
+ | `getPhaseState(phaseName?)` | Reads current or named phase state |
620
+ | `promptByInstanceId(promptId)` | Reads one open prompt instance |
621
+ | `windowByInstanceId(windowId)` | Reads one open window instance |
622
+
623
+ ### `effects`
624
+
625
+ Use `effects` to queue runtime side effects alongside an accepted state.
626
+
627
+ | Effect helper | Notes |
628
+ | --- | --- |
629
+ | `effects.transition(phaseName)` | Transition into another phase |
630
+ | `effects.openPrompt(prompt, { to, resume, title?, payload?, options? })` | Open a prompt and bind a continuation token |
631
+ | `effects.closePrompt(promptId)` | Close one open prompt instance |
632
+ | `effects.openWindow(window, { closePolicy?, addressedTo?, payload?, resume? })` | Open a reducer-owned window |
633
+ | `effects.closeWindow(windowId)` | Close one open window instance |
634
+ | `effects.rollDie(dieId)` | Roll one authored die with runtime-owned RNG |
635
+ | `effects.shuffleSharedZone(zoneId)` | Shuffle a shared zone/deck |
636
+ | `effects.dealCardsToPlayerZone(fromZoneId, playerId, toZoneId, count)` | Deal cards from a shared zone into a player zone |
637
+ | `effects.sample(from, sampleId, resume, count?)` | Sample cards and resume a continuation |
638
+ | `effects.randomInt(min, max, randomIntId, resume)` | Sample one integer and resume a shared continuation |
639
+ | `effects.dispatchSystem(event, payload?)` | Queue a system event immediately |
640
+ | `effects.scheduleTiming(timing, event, payload?)` | Queue a timed system input |
641
+
642
+ Prompt and window instance IDs are branded runtime IDs. Use the values returned
643
+ by the runtime state instead of raw strings.
644
+
645
+ Randomness follows the same rule. Runtime-owned effects such as
646
+ `rollDie(...)`, `shuffleSharedZone(...)`, `sample(...)`, and
647
+ `randomInt(...)` consume seeded interpreter RNG. Authored reducer code should
648
+ not call `Math.random()` directly.
649
+
650
+ Use `effects.rollDie(...)` when the runtime only needs to update an authored
651
+ die. Use `effects.randomInt(...)` when reducer logic needs the sampled value
652
+ back through a shared continuation.
653
+
654
+ ```ts
655
+ const resolveRoll = defineContinuation<GameContract>()({
656
+ data: z.object({}),
657
+ response: z.object({
658
+ randomIntId: z.string(),
659
+ value: z.number().int(),
660
+ }),
661
+ reduce({ state, input, accept }) {
662
+ if (input.source !== "shared") {
663
+ return accept(state);
664
+ }
665
+
666
+ return accept({
667
+ ...state,
668
+ publicState: {
669
+ ...state.publicState,
670
+ lastRoll: input.response.value,
671
+ },
672
+ });
673
+ },
674
+ });
675
+ ```
676
+
677
+ ```ts
678
+ reduce({ state, accept, effects }) {
679
+ return accept(state, [effects.rollDie("turn-die")]);
680
+ }
681
+ ```
682
+
683
+ ```ts
684
+ reduce({ state, accept, effects }) {
685
+ return accept(state, [
686
+ effects.randomInt(1, 6, "turn-roll", resolveRoll({})),
687
+ ]);
688
+ }
689
+ ```
690
+
691
+ ```ts
692
+ enter({ state, accept, effects }) {
693
+ return accept(state, [
694
+ effects.openPrompt(judgePlacementPrompt, {
695
+ to: "player-1",
696
+ resume: resolvePlacement({ pendingCardId: "a-dog" }),
697
+ options: [
698
+ { id: "ring-1", label: "Ring 1" },
699
+ { id: "discard", label: "Not in any ring" },
700
+ ],
701
+ }),
702
+ effects.transition("judgeRings"),
703
+ ]);
704
+ }
705
+ ```
706
+
707
+ ## Table helpers
708
+
709
+ Reducer table helpers are immutable. Each mover returns a cloned table or state.
710
+
711
+ ### Flow and zones
712
+
713
+ | Helper | Returns | Notes |
714
+ | --- | --- | --- |
715
+ | `setActivePlayers(state, playerIds)` | State copy | Replaces `flow.activePlayers` |
716
+ | `setPhaseState(state, phaseName, phaseState)` | State copy | Writes one phase-local state value |
717
+ | `getSharedZoneCards(table, zoneId)` | `readonly string[]` | Reads a shared zone or shared deck |
718
+ | `getPlayerZoneCards(table, playerId, zoneId)` | `readonly string[]` | Reads a per-player zone or hand |
719
+ | `addCardToSharedZone(table, zoneId, cardId, playedBy?)` | Table copy | Appends a card to a shared zone |
720
+ | `removeCardFromSharedZone(table, zoneId, cardId)` | Table copy | Removes a card from a shared zone |
721
+ | `moveCardFromPlayerZoneToSharedZone({ ... })` | Table copy | Moves one card from a player zone to a shared zone |
722
+ | `moveCardBetweenSharedZones({ ... })` | Table copy | Moves one card between shared zones |
723
+
724
+ ```ts
725
+ const handCards = getPlayerZoneCards(state.table, input.playerId, "things-hand");
726
+
727
+ const nextTable = moveCardFromPlayerZoneToSharedZone({
728
+ table: state.table,
729
+ playerId: input.playerId,
730
+ fromZoneId: "things-hand",
731
+ toZoneId: "ring-1",
732
+ cardId: input.params.cardId,
733
+ playedBy: input.playerId,
734
+ });
735
+ ```
736
+
737
+ ### Boards and containers
738
+
739
+ | Helper | Returns | Notes |
740
+ | --- | --- | --- |
741
+ | `getBoard(table, boardId)` | Board state | Works for shared and per-player board IDs |
742
+ | `getSpace(table, boardId, spaceId)` | Space state | Reads one board space |
743
+ | `getContainer(table, boardId, containerId)` | Container state | Reads one board container |
744
+ | `getBoardsByTypeId(table, typeId)` | Board ID array | Finds boards by authored `typeId` |
745
+ | `getSpacesByTypeId(table, boardId, typeId)` | Space ID array | Finds spaces by authored `typeId` |
746
+ | `getRelatedSpaces(table, boardId, spaceId, relationTypeId)` | Space ID array | Traverses typed board relations |
747
+ | `getAdjacentSpaces(table, boardId, spaceId)` | Space ID array | Shortcut for `relationTypeId: "adjacent"` |
748
+ | `getComponentsOnSpace(table, boardId, spaceId)` | Component ID array | Reads ordered occupants on a space |
749
+ | `getComponentsInContainer(table, boardId, containerId)` | Component ID array | Reads ordered occupants in a container |
750
+ | `moveComponentToSpace(table, componentId, boardId, spaceId)` | Table copy | Moves any component onto a board space |
751
+ | `moveComponentToContainer(table, componentId, boardId, containerId)` | Table copy | Moves any component into a board container |
752
+ | `assertCardAllowedInContainer(table, boardId, containerId, componentId)` | `void` | Throws if a card-set restriction would be violated |
753
+
754
+ ### Hex-board helpers
755
+
756
+ | Helper | Returns | Notes |
757
+ | --- | --- | --- |
758
+ | `getHexBoard(table, boardId)` | Hex board state | Narrows a board to `layout: "hex"` |
759
+ | `getHexSpace(table, boardId, spaceId)` | Hex space state | Reads one hex space |
760
+ | `getHexSpaceAt(table, boardId, q, r)` | Hex space or `undefined` | Looks up by axial coordinates |
761
+ | `getEdge(table, boardId, edgeId)` | Edge state | Reads one hex edge |
762
+ | `getVertex(table, boardId, vertexId)` | Vertex state | Reads one hex vertex |
763
+ | `getEdgesByTypeId(table, boardId, typeId)` | Edge ID array | Finds edges by authored `typeId` |
764
+ | `getVerticesByTypeId(table, boardId, typeId)` | Vertex ID array | Finds vertices by authored `typeId` |
765
+ | `moveComponentToEdge(table, componentId, boardId, edgeId)` | Table copy | Moves a component onto a hex edge |
766
+ | `moveComponentToVertex(table, componentId, boardId, vertexId)` | Table copy | Moves a component onto a hex vertex |
767
+
768
+ ## `applySetupBootstrap(...)`
769
+
770
+ `applySetupBootstrap(state, steps)` applies the same bootstrap language used by
771
+ `setupProfiles[*].bootstrap`.
772
+
773
+ Use it when you need the setup bootstrap logic directly, such as in reducer
774
+ runtime tests or custom initialization code.
775
+
776
+ ```ts
777
+ import { applySetupBootstrap } from "@dreamboard/app-sdk/reducer";
778
+
779
+ const nextState = applySetupBootstrap(state, [
780
+ {
781
+ type: "shuffle",
782
+ container: {
783
+ type: "sharedZone",
784
+ zoneId: "draw-deck",
785
+ },
786
+ },
787
+ {
788
+ type: "deal",
789
+ from: {
790
+ type: "sharedZone",
791
+ zoneId: "draw-deck",
792
+ },
793
+ to: {
794
+ type: "playerZone",
795
+ zoneId: "main-hand",
796
+ },
797
+ count: 5,
798
+ },
799
+ ]);
800
+ ```
801
+
802
+ Card-set compatibility is enforced for zones and board containers while bootstrap
803
+ steps run.
804
+
805
+ ## `createReducerBundle(...)`
806
+
807
+ `createReducerBundle(...)` converts a reducer definition into the runtime bundle
808
+ consumed by the Dreamboard runtime.
809
+
810
+ ```ts
811
+ import game from "./game";
812
+ import { createReducerBundle } from "@dreamboard/app-sdk/reducer";
813
+
814
+ export default createReducerBundle(game);
815
+ ```
816
+
817
+ ### Returned runtime methods
818
+
819
+ | Method | Notes |
820
+ | --- | --- |
821
+ | `initialize({ table, playerIds, rngSeed?, setup? })` | Creates the initial reducer state, applies setup overrides, and enters the initial phase |
822
+ | `initializePhase({ state, to })` | Initializes one phase state block |
823
+ | `validateInput({ state, input })` | Validates one runtime input |
824
+ | `reduce({ state, input })` | Applies one input without draining follow-up effects |
825
+ | `dispatch({ state, input })` | Applies one input and drains runtime effects |
826
+ | `getAvailableActions({ state, playerId })` | Returns surfaced action metadata for one player |
827
+ | `getView({ state, playerId, viewId? })` | Projects one named reducer view; defaults to `player` |
828
+
829
+ The bundle also exposes reducer metadata registries such as `actions`,
830
+ `prompts`, `views`, `windows`, `continuations`, and `metadata`.
831
+
832
+ ## High-value type utilities
833
+
834
+ `@dreamboard/app-sdk/reducer` also exports type helpers for reducer authoring.
835
+
836
+ | Type | Use |
837
+ | --- | --- |
838
+ | `GameStateOf<Source>` | Resolved reducer state for a contract or full game definition |
839
+ | `PhaseMapOf<Contract>` | Type-check a `phases` object against the contract's phase-name union |
840
+ | `ViewOfDefinition<Definition, ViewName>` | Inferred payload for one registered view |
841
+ | `ActionParamsOfDefinition<Definition, ActionName>` | Inferred params for a named action across phases |
842
+ | `ActionParamsOfDefinitionPhase<Definition, PhaseName, ActionName>` | Inferred params for one phase-local action |
843
+ | `PromptIdsOfDefinition<Definition>` | Prompt ID union for a reducer definition |
844
+ | `PromptResponseOfDefinition<Definition, PromptId>` | Response payload for one prompt |
845
+ | `WindowActionNamesOfDefinition<Definition, WindowId>` | Window action-name union for one registered window |
846
+ | `WindowActionParamsOfDefinition<Definition, WindowId, ActionName>` | Inferred params payload for one window action |
847
+
848
+ ```ts
849
+ import type {
850
+ ActionParamsOfDefinitionPhase,
851
+ GameStateOf,
852
+ PhaseMapOf,
853
+ ViewOfDefinition,
854
+ } from "@dreamboard/app-sdk/reducer";
855
+
856
+ type GameState = GameStateOf<typeof game>;
857
+ type Phases = PhaseMapOf<typeof gameContract>;
858
+ type PlayerView = ViewOfDefinition<typeof game, "player">;
859
+ type PlaceThingParams = ActionParamsOfDefinitionPhase<
860
+ typeof game,
861
+ "placeThing",
862
+ "placeThing"
863
+ >;
864
+ ```