@dreamboard-games/cli 0.1.30-alpha.0 → 0.1.30-alpha.2

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 (114) hide show
  1. package/README.md +179 -22
  2. package/dist/{chunk-TSJVWTJO.js → chunk-N7XPNNUI.js} +14 -12
  3. package/dist/chunk-N7XPNNUI.js.map +1 -0
  4. package/dist/chunk-SEGVTWSK.js +44 -0
  5. package/dist/{chunk-3XNJT3RK.js → chunk-TAQKH67O.js} +21279 -35845
  6. package/dist/chunk-TAQKH67O.js.map +1 -0
  7. package/dist/{global-config-UKSWNDTX.js → global-config-S4ZIPECE.js} +3 -3
  8. package/dist/index.js +955 -230
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal.js +3 -4
  11. package/dist/{agent-verifier/keychain-backend-TNOPQV3Z.mjs → keychain-backend-HDF4TZDL.js} +2 -1
  12. package/dist/{agent-verifier/prompt-3BAINGAQ.mjs → prompt-NDV3AE5L.js} +2 -1
  13. package/package.json +6 -6
  14. package/skills/dreamboard/references/building-your-first-game.md +510 -0
  15. package/skills/dreamboard/references/cli.md +104 -0
  16. package/skills/dreamboard/references/game-interface.md +548 -0
  17. package/skills/dreamboard/references/manifest-authoring.md +597 -0
  18. package/skills/dreamboard/references/quickstart.md +66 -0
  19. package/skills/dreamboard/references/reducer.md +864 -0
  20. package/skills/dreamboard/references/rule-authoring.md +147 -0
  21. package/skills/dreamboard/references/testing.md +249 -0
  22. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  23. package/dist/agent-verifier/agent-workspace-verifier.mjs +0 -227
  24. package/dist/agent-verifier/chunk-2E5P5NWG.mjs +0 -835
  25. package/dist/agent-verifier/chunk-2GBBP27W.mjs +0 -301
  26. package/dist/agent-verifier/chunk-2NZNKIND.mjs +0 -166
  27. package/dist/agent-verifier/chunk-2QMNAVV4.mjs +0 -14522
  28. package/dist/agent-verifier/chunk-2SZHMP6F.mjs +0 -264
  29. package/dist/agent-verifier/chunk-54TAYXUD.mjs +0 -12
  30. package/dist/agent-verifier/chunk-6A5HRJMQ.mjs +0 -3174
  31. package/dist/agent-verifier/chunk-6UUJEYDV.mjs +0 -213
  32. package/dist/agent-verifier/chunk-7653FPGJ.mjs +0 -381
  33. package/dist/agent-verifier/chunk-BVVNBJM4.mjs +0 -221
  34. package/dist/agent-verifier/chunk-CEDUHGNH.mjs +0 -74
  35. package/dist/agent-verifier/chunk-CEQ2VJWN.mjs +0 -149
  36. package/dist/agent-verifier/chunk-CFU5EWIC.mjs +0 -69
  37. package/dist/agent-verifier/chunk-DTMJCPS4.mjs +0 -730
  38. package/dist/agent-verifier/chunk-EIQWDQWJ.mjs +0 -186
  39. package/dist/agent-verifier/chunk-EOQIV6PS.mjs +0 -649
  40. package/dist/agent-verifier/chunk-HBNDKQT5.mjs +0 -8381
  41. package/dist/agent-verifier/chunk-HJFQDSTU.mjs +0 -225
  42. package/dist/agent-verifier/chunk-LI3ZR3BI.mjs +0 -41
  43. package/dist/agent-verifier/chunk-LM3OZLZG.mjs +0 -48
  44. package/dist/agent-verifier/chunk-MINCYHXN.mjs +0 -106
  45. package/dist/agent-verifier/chunk-MRCUP5SW.mjs +0 -128
  46. package/dist/agent-verifier/chunk-PM3SVG6R.mjs +0 -38
  47. package/dist/agent-verifier/chunk-RBDDIIPM.mjs +0 -19
  48. package/dist/agent-verifier/chunk-RJBLBYHX.mjs +0 -1681
  49. package/dist/agent-verifier/chunk-SHUMAVAP.mjs +0 -59
  50. package/dist/agent-verifier/chunk-SYPLYRGB.mjs +0 -2812
  51. package/dist/agent-verifier/chunk-U6OJN7XS.mjs +0 -8092
  52. package/dist/agent-verifier/chunk-VYJTHSYR.mjs +0 -44
  53. package/dist/agent-verifier/chunk-XYDL7GY6.mjs +0 -10
  54. package/dist/agent-verifier/compile-WNCQQVOF.mjs +0 -313
  55. package/dist/agent-verifier/global-config-WX3ZZIVU.mjs +0 -17
  56. package/dist/agent-verifier/local-files-MTPLP62S.mjs +0 -46
  57. package/dist/agent-verifier/local-typecheck-QFYYAZOK.mjs +0 -9
  58. package/dist/agent-verifier/materialize-workspace-EWGZIVOY.mjs +0 -90
  59. package/dist/agent-verifier/project-state-7GR6BQTQ.mjs +0 -32
  60. package/dist/agent-verifier/reducer-bundle-preflight-C73LEXI2.mjs +0 -23
  61. package/dist/agent-verifier/reducer-contract-preflight-22X7DSZW.mjs +0 -10
  62. package/dist/agent-verifier/reducer-native-test-harness-GMWBUISX.mjs +0 -53
  63. package/dist/agent-verifier/static-scaffold-4YEQME5N.mjs +0 -28
  64. package/dist/agent-verifier/sync-LOQAH4RC.mjs +0 -594
  65. package/dist/agent-verifier/test-YOJERVHN.mjs +0 -356
  66. package/dist/agent-verifier/testing-5K2BJYF2.mjs +0 -674
  67. package/dist/agent-verifier/workspace-codegen-JDZJRSDV.mjs +0 -11
  68. package/dist/agent-verifier/workspace-dependencies-HZ6VVS4G.mjs +0 -14
  69. package/dist/chunk-2H7UOFLK.js +0 -11
  70. package/dist/chunk-3XNJT3RK.js.map +0 -1
  71. package/dist/chunk-7FOO4AJI.js +0 -50
  72. package/dist/chunk-7FOO4AJI.js.map +0 -1
  73. package/dist/chunk-TSJVWTJO.js.map +0 -1
  74. package/dist/internal.d.ts +0 -311
  75. package/dist/keychain-backend-JHTXAKWC.js +0 -135
  76. package/dist/prompt-GMZABCJC.js +0 -756
  77. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +0 -71
  78. package/dist/runtime-packages/ui-host-runtime/src/browser-interaction.ts +0 -139
  79. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +0 -374
  80. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +0 -266
  81. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +0 -212
  82. package/dist/runtime-packages/ui-host-runtime/src/components/host-primitives.tsx +0 -271
  83. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +0 -135
  84. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +0 -5
  85. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +0 -194
  86. package/dist/runtime-packages/ui-host-runtime/src/gameplay-authority-transport.ts +0 -626
  87. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +0 -1
  88. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +0 -1
  89. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +0 -294
  90. package/dist/runtime-packages/ui-host-runtime/src/index.ts +0 -3
  91. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +0 -11
  92. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +0 -324
  93. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +0 -195
  94. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +0 -138
  95. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +0 -159
  96. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +0 -551
  97. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +0 -13
  98. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +0 -122
  99. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +0 -26
  100. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +0 -583
  101. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +0 -219
  102. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +0 -117
  103. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +0 -431
  104. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +0 -211
  105. package/dist/runtime-packages/ui-host-runtime/src/session-recovery.ts +0 -80
  106. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +0 -1034
  107. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +0 -416
  108. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +0 -184
  109. package/dist/testing-KLSV6CPJ.js +0 -674
  110. package/dist/testing-KLSV6CPJ.js.map +0 -1
  111. /package/dist/{chunk-2H7UOFLK.js.map → chunk-SEGVTWSK.js.map} +0 -0
  112. /package/dist/{global-config-UKSWNDTX.js.map → global-config-S4ZIPECE.js.map} +0 -0
  113. /package/dist/{keychain-backend-JHTXAKWC.js.map → keychain-backend-HDF4TZDL.js.map} +0 -0
  114. /package/dist/{prompt-GMZABCJC.js.map → prompt-NDV3AE5L.js.map} +0 -0
package/dist/internal.js CHANGED
@@ -16,16 +16,15 @@ import {
16
16
  shortHash,
17
17
  updateProjectState,
18
18
  writeSnapshot
19
- } from "./chunk-3XNJT3RK.js";
19
+ } from "./chunk-TAQKH67O.js";
20
20
  import {
21
21
  ENVIRONMENT_CONFIGS,
22
22
  getStoredSession,
23
23
  loadGlobalConfig,
24
24
  readJsonFile,
25
25
  writeJsonFile
26
- } from "./chunk-TSJVWTJO.js";
27
- import "./chunk-7FOO4AJI.js";
28
- import "./chunk-2H7UOFLK.js";
26
+ } from "./chunk-N7XPNNUI.js";
27
+ import "./chunk-SEGVTWSK.js";
29
28
  export {
30
29
  CONFIG_FLAG_ARGS,
31
30
  ENVIRONMENT_CONFIGS,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-XYDL7GY6.mjs";
2
+ import "./chunk-SEGVTWSK.js";
3
3
 
4
4
  // src/config/keychain-backend.ts
5
5
  var KEYCHAIN_SERVICE = "dreamboard-cli";
@@ -132,3 +132,4 @@ export {
132
132
  _setKeyringModuleForTests,
133
133
  tryKeychainBackend
134
134
  };
135
+ //# sourceMappingURL=keychain-backend-HDF4TZDL.js.map
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-XYDL7GY6.mjs";
2
+ import "./chunk-SEGVTWSK.js";
3
3
 
4
4
  // ../../node_modules/.pnpm/consola@3.4.2/node_modules/consola/dist/chunks/prompt.mjs
5
5
  import "util";
@@ -753,3 +753,4 @@ export {
753
753
  kCancel,
754
754
  prompt
755
755
  };
756
+ //# sourceMappingURL=prompt-NDV3AE5L.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamboard-games/cli",
3
- "version": "0.1.30-alpha.0",
3
+ "version": "0.1.30-alpha.2",
4
4
  "description": "Design board games with AI and turn ideas into playable digital prototypes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,6 @@
12
12
  "types": "./dist/internal.d.ts",
13
13
  "default": "./dist/internal.js"
14
14
  },
15
- "./agent-workspace-verifier": "./dist/agent-verifier/agent-workspace-verifier.mjs",
16
15
  "./package.json": "./package.json"
17
16
  },
18
17
  "files": [
@@ -34,6 +33,10 @@
34
33
  "access": "public"
35
34
  },
36
35
  "dependencies": {
36
+ "@dreamboard-games/api-client": "0.3.0-alpha.3",
37
+ "@dreamboard-games/gameplay-authority-client": "0.1.0-alpha.1",
38
+ "@dreamboard-games/sdk": "0.4.0-alpha.0",
39
+ "@dreamboard-games/ui-host-runtime": "0.1.0-alpha.2",
37
40
  "@tailwindcss/postcss": "^4.3.0",
38
41
  "@vitejs/plugin-react": "^4.7.0",
39
42
  "citty": "^0.2.2",
@@ -52,10 +55,7 @@
52
55
  "vaul": "^1.1.2",
53
56
  "vite": "^5.4.21",
54
57
  "zod": "4.4.3",
55
- "zustand": "^5.0.14",
56
- "@dreamboard-games/api-client": "0.3.0-alpha.3",
57
- "@dreamboard-games/sdk": "0.4.0-alpha.0",
58
- "playwright": "^1.60.0"
58
+ "zustand": "^5.0.14"
59
59
  },
60
60
  "optionalDependencies": {
61
61
  "@napi-rs/keyring": "^1.3.0"
@@ -0,0 +1,510 @@
1
+ <!-- Generated by apps/dreamboard-cli/scripts/sync-skill-docs.ts. -->
2
+ <!-- Source: docs/tutorials/building-your-first-game.mdx -->
3
+
4
+ # Building your first game
5
+
6
+ Tutorial: build a complete Dreamboard game from rule to tests.
7
+
8
+ This tutorial walks through a deliberately small Dreamboard game so you can see
9
+ the whole authoring loop once: rules, manifest, reducer, UI, and tests.
10
+
11
+ The example game is `Race to Ten`.
12
+
13
+ - 2 players
14
+ - one shared d6
15
+ - each turn, the active player rolls the die and adds the result to their score
16
+ - the first player to reach 10 points wins
17
+
18
+ For tutorial stability, the main walkthrough uses a deterministic die sequence
19
+ `1, 2, 3, 4, 5, 6, ...` instead of true randomness. The important part is how a
20
+ die component, a `rollDie` action, reducer state, UI, and tests fit together.
21
+ This is also consistent with the reducer model: reducers stay pure, and
22
+ runtime-owned effects handle live randomness.
23
+
24
+ ## What you will build
25
+
26
+ By the end of the tutorial you will have:
27
+
28
+ - a `rule.md` that explains the game
29
+ - a `manifest.json` with a shared die
30
+ - reducer code in `app/`
31
+ - a playable `ui/App.tsx`
32
+ - a base and scenario under `test/`
33
+
34
+ ## Prerequisites
35
+
36
+ - Dreamboard CLI installed: `npm install -g dreamboard`
37
+ - authenticated with `dreamboard login`
38
+
39
+ ## 1. Create the workspace
40
+
41
+ ```bash
42
+ dreamboard new race-to-ten --description "A tiny scoring game with one shared die"
43
+ cd race-to-ten
44
+ ```
45
+
46
+ The scaffold gives you the files you will edit next:
47
+
48
+ - `rule.md`
49
+ - `manifest.json`
50
+ - `app/game-contract.ts`
51
+ - `app/game.ts`
52
+ - `app/phases/*`
53
+ - `ui/App.tsx`
54
+ - `test/bases/*`
55
+ - `test/scenarios/*`
56
+
57
+ ## 2. Write `rule.md`
58
+
59
+ ```md
60
+ # Race to Ten
61
+
62
+ ## Overview
63
+
64
+ - Players: 2
65
+ - Objective: be the first player to reach 10 points
66
+ - Duration: 3 to 5 minutes
67
+
68
+ ## Components
69
+
70
+ - 1 shared six-sided die
71
+ - visible score totals for each player
72
+ - no hidden information
73
+
74
+ ## Setup
75
+
76
+ 1. Seat two players.
77
+ 2. Set both scores to 0.
78
+ 3. Clear the die value.
79
+ 4. Player 1 takes the first turn.
80
+
81
+ ## Gameplay
82
+
83
+ ### Phase 1: takeTurn
84
+
85
+ - Acting player: the current player only
86
+ - Allowed actions: `rollDie`
87
+ - Validation: only the active player may act, and no actions are legal after a winner exists
88
+ - Completion:
89
+ - `rollDie` sets the shared die to a new value
90
+ - the acting player adds that value to their score
91
+ - if the acting player reaches 10 points, the game ends immediately
92
+ - otherwise the turn passes to the other player
93
+
94
+ ## Scoring and progression
95
+
96
+ - `rollDie` increases the acting player's score by the rolled value
97
+
98
+ ## Winning conditions
99
+
100
+ - End trigger: a player reaches 10 points
101
+ - Winner determination: the player who reached 10 points wins
102
+ - Tie-breaker: not applicable because turns resolve one at a time
103
+
104
+ ## Special rules and edge cases
105
+
106
+ - Actions after game end are illegal
107
+ - Out-of-turn actions are illegal
108
+ - For this tutorial implementation, the die value cycles deterministically from 1 to 6 so the example stays reproducible
109
+ ```
110
+
111
+ For a fuller reference, see [Rule authoring](./rule-authoring.md).
112
+
113
+ ## 3. Write `manifest.json`
114
+
115
+ This game needs player-count metadata and one shared die.
116
+
117
+ ```json
118
+ {
119
+ "players": {
120
+ "minPlayers": 2,
121
+ "maxPlayers": 2,
122
+ "optimalPlayers": 2
123
+ },
124
+ "dieTypes": [
125
+ {
126
+ "id": "standard-d6",
127
+ "name": "Standard d6",
128
+ "sides": 6
129
+ }
130
+ ],
131
+ "dieSeeds": [
132
+ {
133
+ "id": "turn-die",
134
+ "name": "Turn die",
135
+ "typeId": "standard-d6"
136
+ }
137
+ ]
138
+ }
139
+ ```
140
+
141
+ Run:
142
+
143
+ ```bash
144
+ dreamboard sync
145
+ dreamboard compile
146
+ ```
147
+
148
+ `dreamboard sync` refreshes generated contracts from authored files.
149
+ `dreamboard compile` builds the current authored head.
150
+
151
+ ## 4. Define the reducer contract
152
+
153
+ Open `app/game-contract.ts` and replace the empty schemas with the state the
154
+ rules require.
155
+
156
+ ```ts
157
+ import { z } from "zod";
158
+ import * as manifestContract from "../shared/manifest-contract";
159
+ import { defineGameContract, type GameStateOf } from "@dreamboard/app-sdk/reducer";
160
+
161
+ const playerId = manifestContract.ids.playerId;
162
+
163
+ const publicStateSchema = z.object({
164
+ currentPlayerId: playerId,
165
+ winnerPlayerId: playerId.nullable(),
166
+ lastRoll: z.number().int().min(1).max(6).nullable(),
167
+ scores: z.record(playerId, z.number().int().nonnegative()),
168
+ });
169
+
170
+ const privateStateSchema = z.object({});
171
+
172
+ const hiddenStateSchema = z.object({
173
+ rollCount: z.number().int().nonnegative(),
174
+ });
175
+
176
+ export const gameContract = defineGameContract({
177
+ manifest: manifestContract.manifestContract,
178
+ state: {
179
+ public: publicStateSchema,
180
+ private: privateStateSchema,
181
+ hidden: hiddenStateSchema,
182
+ initial: {
183
+ public: ({ playerIds }) => ({
184
+ currentPlayerId: playerIds[0],
185
+ winnerPlayerId: null,
186
+ lastRoll: null,
187
+ scores: Object.fromEntries(playerIds.map((playerId) => [playerId, 0])),
188
+ }),
189
+ private: () => ({}),
190
+ hidden: () => ({
191
+ rollCount: 0,
192
+ }),
193
+ },
194
+ },
195
+ });
196
+
197
+ export type GameContract = typeof gameContract;
198
+ export type GameState = GameStateOf<GameContract>;
199
+ ```
200
+
201
+ The public state holds player-visible game progress. The hidden state tracks the
202
+ deterministic roll index so the test can stay reproducible.
203
+
204
+ ## 5. Add a player view
205
+
206
+ Create `app/player-view.ts`:
207
+
208
+ ```ts
209
+ import { z } from "zod";
210
+ import { defineView } from "@dreamboard/app-sdk/reducer";
211
+ import type { GameContract } from "./game-contract";
212
+
213
+ export const playerView = defineView<GameContract>()({
214
+ schema: z.object({
215
+ currentPlayerId: z.string(),
216
+ winnerPlayerId: z.string().nullable(),
217
+ lastRoll: z.number().nullable(),
218
+ scores: z.record(z.string(), z.number()),
219
+ isMyTurn: z.boolean(),
220
+ targetScore: z.number(),
221
+ turnDieId: z.string(),
222
+ }),
223
+ project({ state, playerId }) {
224
+ return {
225
+ currentPlayerId: state.publicState.currentPlayerId,
226
+ winnerPlayerId: state.publicState.winnerPlayerId,
227
+ lastRoll: state.publicState.lastRoll,
228
+ scores: state.publicState.scores,
229
+ isMyTurn: state.publicState.currentPlayerId === playerId,
230
+ targetScore: 10,
231
+ turnDieId: "turn-die",
232
+ };
233
+ },
234
+ });
235
+ ```
236
+
237
+ The UI will read this view instead of reconstructing game logic in React.
238
+
239
+ ## 6. Implement the phase and action
240
+
241
+ Create `app/phases/take-turn.ts`:
242
+
243
+ ```ts
244
+ import { z } from "zod";
245
+ import { defineAction, definePhase, setActivePlayers } from "@dreamboard/app-sdk/reducer";
246
+ import type { GameContract } from "../game-contract";
247
+
248
+ const TARGET_SCORE = 10;
249
+ const TURN_DIE_ID = "turn-die" as const;
250
+
251
+ const rollDie = defineAction<GameContract>()({
252
+ params: z.object({}),
253
+ validate({ state, input }) {
254
+ if (state.publicState.winnerPlayerId) {
255
+ return {
256
+ errorCode: "GAME_ALREADY_ENDED",
257
+ message: "The game has already ended.",
258
+ };
259
+ }
260
+
261
+ if (input.playerId !== state.publicState.currentPlayerId) {
262
+ return {
263
+ errorCode: "NOT_YOUR_TURN",
264
+ message: "It is not your turn.",
265
+ };
266
+ }
267
+
268
+ return null;
269
+ },
270
+ reduce({ state, input, accept }) {
271
+ const nextRoll = (state.hiddenState.rollCount % 6) + 1;
272
+ const nextScore = state.publicState.scores[input.playerId] + nextRoll;
273
+ const nextScores = {
274
+ ...state.publicState.scores,
275
+ [input.playerId]: nextScore,
276
+ };
277
+
278
+ const nextPlayerId =
279
+ input.playerId === "player-1" ? "player-2" : "player-1";
280
+
281
+ const nextTable = {
282
+ ...state.table,
283
+ dice: {
284
+ ...state.table.dice,
285
+ [TURN_DIE_ID]: {
286
+ ...state.table.dice[TURN_DIE_ID],
287
+ value: nextRoll,
288
+ },
289
+ },
290
+ };
291
+
292
+ return accept({
293
+ ...state,
294
+ table: nextTable,
295
+ publicState: {
296
+ ...state.publicState,
297
+ currentPlayerId: nextScore >= TARGET_SCORE ? input.playerId : nextPlayerId,
298
+ winnerPlayerId: nextScore >= TARGET_SCORE ? input.playerId : null,
299
+ lastRoll: nextRoll,
300
+ scores: nextScores,
301
+ },
302
+ hiddenState: {
303
+ ...state.hiddenState,
304
+ rollCount: state.hiddenState.rollCount + 1,
305
+ },
306
+ });
307
+ },
308
+ });
309
+
310
+ export const takeTurn = definePhase<GameContract>()({
311
+ kind: "player",
312
+ state: z.object({}),
313
+ initialState: () => ({}),
314
+ enter({ state, accept }) {
315
+ if (state.publicState.winnerPlayerId) {
316
+ return accept(state);
317
+ }
318
+
319
+ return accept(
320
+ setActivePlayers(state, [state.publicState.currentPlayerId]),
321
+ );
322
+ },
323
+ actions: {
324
+ rollDie,
325
+ },
326
+ });
327
+ ```
328
+
329
+ Then update `app/phases/index.ts`:
330
+
331
+ ```ts
332
+ import { takeTurn } from "./take-turn";
333
+
334
+ export const phases = {
335
+ takeTurn,
336
+ };
337
+ ```
338
+
339
+ ## 7. Register the game
340
+
341
+ Update `app/game.ts`:
342
+
343
+ ```ts
344
+ import { defineGame } from "@dreamboard/app-sdk/reducer";
345
+ import { gameContract } from "./game-contract";
346
+ import { phases } from "./phases";
347
+ import { playerView } from "./player-view";
348
+
349
+ export default defineGame({
350
+ contract: gameContract,
351
+ initialPhase: "takeTurn",
352
+ phases,
353
+ views: {
354
+ player: playerView,
355
+ },
356
+ });
357
+ ```
358
+
359
+ At this point the rules exist, but the UI is still the scaffold placeholder.
360
+
361
+ ## 8. Build the UI
362
+
363
+ Update `ui/App.tsx`:
364
+
365
+ ```tsx
366
+ import { DiceRoller, useActions, useDice, useGameView } from "@dreamboard/ui-sdk";
367
+
368
+ export default function App() {
369
+ const view = useGameView();
370
+ const phase = useActions();
371
+ const dice = useDice(["turn-die"]);
372
+
373
+ const rollDie = async () => {
374
+ if (phase.phase !== "takeTurn") {
375
+ return;
376
+ }
377
+
378
+ await phase.dispatch(phase.commands.rollDie());
379
+ };
380
+
381
+ return (
382
+ <main style={{ padding: 24, fontFamily: "system-ui, sans-serif" }}>
383
+ <h1>Race to Ten</h1>
384
+ <p>First to {view.targetScore} points wins.</p>
385
+
386
+ <DiceRoller
387
+ values={dice.values}
388
+ diceCount={1}
389
+ render={({ values }) => (
390
+ <div style={{ fontSize: 32, marginBottom: 16 }}>
391
+ Die: {values?.[0] ?? "?"}
392
+ </div>
393
+ )}
394
+ />
395
+
396
+ <ul>
397
+ {Object.entries(view.scores).map(([playerId, score]) => (
398
+ <li key={playerId}>
399
+ {playerId}: {score}
400
+ {view.currentPlayerId === playerId ? " <- current player" : ""}
401
+ </li>
402
+ ))}
403
+ </ul>
404
+
405
+ <p>Last roll: {view.lastRoll ?? "not rolled yet"}</p>
406
+
407
+ {view.winnerPlayerId ? (
408
+ <p>Winner: {view.winnerPlayerId}</p>
409
+ ) : (
410
+ <button onClick={() => void rollDie()} disabled={!view.isMyTurn}>
411
+ Roll die
412
+ </button>
413
+ )}
414
+ </main>
415
+ );
416
+ }
417
+ ```
418
+
419
+ The important part is that:
420
+
421
+ - the die exists in authored game structure
422
+ - the reducer updates the die value and the score together
423
+ - the UI reads projected view data and the die state from the runtime
424
+ - actions still go through `useActions()`
425
+
426
+ ## 9. Add a base and a scenario
427
+
428
+ Replace `test/bases/initial-turn.base.ts`:
429
+
430
+ ```ts
431
+ import { defineBase } from "../testing-types";
432
+
433
+ export default defineBase({
434
+ id: "initial-turn",
435
+ seed: 1337,
436
+ players: 2,
437
+ setup: async ({ game }) => {
438
+ await game.start();
439
+ },
440
+ });
441
+ ```
442
+
443
+ Create `test/scenarios/player-two-wins.scenario.ts`:
444
+
445
+ ```ts
446
+ import { defineScenario } from "../testing-types";
447
+
448
+ export default defineScenario({
449
+ id: "player-two-wins",
450
+ description: "The deterministic roll sequence lets player 2 reach ten first",
451
+ from: "initial-turn",
452
+ when: async ({ game }) => {
453
+ await game.action("player-1", "rollDie", {});
454
+ await game.action("player-2", "rollDie", {});
455
+ await game.action("player-1", "rollDie", {});
456
+ await game.action("player-2", "rollDie", {});
457
+ await game.action("player-1", "rollDie", {});
458
+ await game.action("player-2", "rollDie", {});
459
+ },
460
+ then: ({ publicState, view, expect, history, phase }) => {
461
+ const state = publicState();
462
+
463
+ expect(phase()).toBe("takeTurn");
464
+ expect(state.lastRoll).toBe(6);
465
+ expect(state.scores["player-1"]).toBe(9);
466
+ expect(state.scores["player-2"]).toBe(12);
467
+ expect(state.winnerPlayerId).toBe("player-2");
468
+ expect(view("player-2").winnerPlayerId).toBe("player-2");
469
+ expect(history().accepted().length).toBe(6);
470
+ },
471
+ });
472
+ ```
473
+
474
+ Generate artifacts and run the test suite:
475
+
476
+ ```bash
477
+ dreamboard test generate
478
+ dreamboard test run
479
+ ```
480
+
481
+ ## 10. Run the game locally
482
+
483
+ Use the local runtime to verify the same flow manually:
484
+
485
+ ```bash
486
+ dreamboard run
487
+ ```
488
+
489
+ If you edit `rule.md` or `manifest.json`, run `dreamboard sync` again before
490
+ continuing.
491
+
492
+ ## Where to go next
493
+
494
+ This tutorial uses the smallest die-based loop that still touches authored
495
+ components, reducer state, UI, and tests. The next layer of depth is:
496
+
497
+ - add setup profiles or setup options
498
+ - add richer reducer state and more than one phase
499
+ - replace the plain button UI with grouped action panels and richer views
500
+ - add rejection-path tests such as out-of-turn actions
501
+
502
+ If you need live randomness in authored reducer logic, do not call
503
+ `Math.random()` inside reducers. Use runtime-owned effects instead:
504
+
505
+ - `effects.rollDie(...)` when the runtime only needs to update an authored die
506
+ - `effects.randomInt(...)` when reducer logic needs the sampled value back
507
+
508
+ The main walkthrough stays deterministic so the rule, reducer, UI, and test
509
+ snippets all line up exactly. See [Reducer](./reducer.md) for the
510
+ runtime-random versions.
@@ -0,0 +1,104 @@
1
+ <!-- Generated by apps/dreamboard-cli/scripts/sync-skill-docs.ts. -->
2
+ <!-- Source: docs/reference/cli.mdx -->
3
+
4
+ # CLI
5
+
6
+ Reference for Dreamboard CLI workflows and commands.
7
+
8
+ Dreamboard CLI manages the authored workspace loop: authenticate, create or
9
+ clone a game, sync authored changes, compile, run, and test.
10
+
11
+ ## Responsibility
12
+
13
+ Use the CLI for:
14
+
15
+ - authenticating the current machine
16
+ - creating or cloning game workspaces
17
+ - syncing authored files to the remote authoring head
18
+ - compiling the current authored head
19
+ - inspecting local versus remote state
20
+ - pulling remote authored changes into the workspace
21
+ - starting or observing playable sessions
22
+ - generating and running reducer-native tests
23
+
24
+ ## Install targets
25
+
26
+ Use the published package for normal game work:
27
+
28
+ ```bash
29
+ npm install -g dreamboard
30
+ dreamboard login
31
+ ```
32
+
33
+ ## Command flow
34
+
35
+ Use this sequence for the normal authored loop:
36
+
37
+ 1. `dreamboard login`
38
+ 2. `dreamboard new ...` or `dreamboard clone ...`
39
+ 3. edit `rule.md`, `manifest.json`, and authored source files
40
+ 4. `dreamboard sync`
41
+ 5. `dreamboard compile`
42
+ 6. `dreamboard run` or `dreamboard start`
43
+ 7. `dreamboard test generate`
44
+ 8. `dreamboard test run`
45
+
46
+ When the remote authored head moves first, run `dreamboard pull` before the
47
+ next sync or compile. Use `dreamboard status` to decide whether the workspace is
48
+ ahead, behind, or diverged.
49
+
50
+ ## Workspace commands
51
+
52
+ | Command | Use it for |
53
+ | --- | --- |
54
+ | `dreamboard login` | Open browser login and store a refreshable session |
55
+ | `dreamboard new <slug> --description "..."` | Create a new game and scaffold a local workspace |
56
+ | `dreamboard clone <slug>` | Clone an existing game into a new local workspace |
57
+ | `dreamboard sync` | Upload authored changes and refresh scaffolded files |
58
+ | `dreamboard compile` | Compile the current remote authored head |
59
+ | `dreamboard status` | Compare local authored and compile state with remote |
60
+ | `dreamboard pull` | Reconcile remote authored changes into the workspace |
61
+
62
+ ```bash
63
+ dreamboard new race-to-ten --description "A tiny scoring game"
64
+ dreamboard sync
65
+ dreamboard compile
66
+ dreamboard status --json
67
+ ```
68
+
69
+ ## Session commands
70
+
71
+ Use session commands after you have a successful compile for the current
72
+ authored head.
73
+
74
+ | Command | Use it for |
75
+ | --- | --- |
76
+ | `dreamboard run` | Start or resume a playable session and observe it from the CLI |
77
+ | `dreamboard start` | Start a fresh session and open it in the browser |
78
+
79
+ ```bash
80
+ dreamboard run
81
+ dreamboard run --players 4 --seed 1337
82
+ dreamboard run --scenario path/to/scenario.json
83
+ dreamboard start --players 2
84
+ ```
85
+
86
+ `dreamboard run` reuses the previous active session by default. Use
87
+ `--new-session` when you want a fresh session instead.
88
+
89
+ ## Test commands
90
+
91
+ Use the scaffolded reducer-native test workspace for repeatable game assertions.
92
+
93
+ ```bash
94
+ dreamboard test generate
95
+ dreamboard test run
96
+ dreamboard test run --scenario test/scenarios/player-two-wins.scenario.ts
97
+ ```
98
+
99
+ ## Start local server
100
+ ```bash
101
+ dreamboard dev
102
+ ```
103
+
104
+ See [Testing](./testing.md) for the scenario and base-file format.