dreamboard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +239 -0
  2. package/dist/chunk-2H7UOFLK.js +11 -0
  3. package/dist/chunk-2H7UOFLK.js.map +1 -0
  4. package/dist/chunk-FK6CWXQR.js +3479 -0
  5. package/dist/chunk-FK6CWXQR.js.map +1 -0
  6. package/dist/chunk-MP7IBNWW.js +13289 -0
  7. package/dist/chunk-MP7IBNWW.js.map +1 -0
  8. package/dist/dist-B3R64F6G.js +99 -0
  9. package/dist/dist-B3R64F6G.js.map +1 -0
  10. package/dist/embedded-harness-PF2LCIWC.js +345 -0
  11. package/dist/embedded-harness-PF2LCIWC.js.map +1 -0
  12. package/dist/index.js +25773 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/prompt-GMZABCJC.js +756 -0
  15. package/dist/prompt-GMZABCJC.js.map +1 -0
  16. package/package.json +40 -0
  17. package/skills/dreamboard/SKILL.md +119 -0
  18. package/skills/dreamboard/references/adversarial-testing.md +113 -0
  19. package/skills/dreamboard/references/all-players-tracking.md +75 -0
  20. package/skills/dreamboard/references/api-reference.md +193 -0
  21. package/skills/dreamboard/references/app-best-practices.md +86 -0
  22. package/skills/dreamboard/references/hands-vs-decks.md +86 -0
  23. package/skills/dreamboard/references/manifest-authoring.md +590 -0
  24. package/skills/dreamboard/references/phase-handlers.md +134 -0
  25. package/skills/dreamboard/references/rule-authoring.md +142 -0
  26. package/skills/dreamboard/references/scenario-format.md +99 -0
  27. package/skills/dreamboard/references/test-harness.md +225 -0
  28. package/skills/dreamboard/references/tts-migration-and-extractor.md +91 -0
  29. package/skills/dreamboard/references/ui-best-practices.md +158 -0
  30. package/skills/dreamboard/references/ui-genre-resource-management.md +187 -0
  31. package/skills/dreamboard/references/ui-genre-trick-taking.md +110 -0
  32. package/skills/dreamboard/references/ui-genre-worker-placement.md +143 -0
  33. package/skills/dreamboard/references/ui-style-guide.md +54 -0
  34. package/skills/dreamboard/scripts/events-extract.mjs +218 -0
  35. package/skills/dreamboard/scripts/extract_tts.py +1178 -0
@@ -0,0 +1,590 @@
1
+ # Manifest Authoring Guide
2
+
3
+ `manifest.json` is the source of truth for your game's structure — components, actions, state machine, and variables. After editing, run `dreamboard update` to regenerate scaffolded files (`app/phases/`, `shared/manifest.d.ts`, etc.).
4
+
5
+ ## Top-Level Structure
6
+
7
+ ```json
8
+ {
9
+ "version": "1.0.0",
10
+ "playerConfig": { ... },
11
+ "deckDefinitions": [ ... ],
12
+ "playerHandDefinitions": [ ... ],
13
+ "components": [ ... ],
14
+ "resources": [ ... ],
15
+ "boardDefinitions": [ ... ],
16
+ "availableActions": [ ... ],
17
+ "stateMachine": { ... },
18
+ "variableSchema": { ... }
19
+ }
20
+ ```
21
+
22
+ | Field | Required | Description |
23
+ | ----------------------- | -------- | -------------------------------------------------- |
24
+ | `version` | ✅ | Manifest version - increment manually when updated |
25
+ | `playerConfig` | ✅ | Min/max/optimal player counts |
26
+ | `deckDefinitions` | ✅ | Card deck blueprints (preset or manual) |
27
+ | `playerHandDefinitions` | ✅ | Per-player card containers |
28
+ | `components` | ✅ | Shared game components (decks, dice) |
29
+ | `resources` | ❌ | Typed resource economy (gold, wood) |
30
+ | `boardDefinitions` | ❌ | Spatial boards (hex, network, square, track) |
31
+ | `availableActions` | ✅ | Player submission buttons |
32
+ | `stateMachine` | ✅ | Game phases and transitions |
33
+ | `variableSchema` | ✅ | Minimal state for game logic |
34
+
35
+ ---
36
+
37
+ ## Authoring Sequence
38
+
39
+ Work through sections in this order. Skip any step that doesn't apply to your game.
40
+
41
+ ### 1. `playerConfig`
42
+
43
+ ```json
44
+ {
45
+ "playerConfig": {
46
+ "minPlayers": 2,
47
+ "maxPlayers": 4,
48
+ "optimalPlayers": 4
49
+ }
50
+ }
51
+ ```
52
+
53
+ | Field | Type | Range | Description |
54
+ | ---------------- | ------- | ----- | ------------------------------------ |
55
+ | `minPlayers` | integer | 1–10 | Minimum players required |
56
+ | `maxPlayers` | integer | 1–10 | Maximum players supported |
57
+ | `optimalPlayers` | integer | 1–10 | Best player count for the experience |
58
+
59
+ ### 2. `deckDefinitions`
60
+
61
+ Deck definitions are blueprints for card types. There are two kinds:
62
+
63
+ #### Preset decks
64
+
65
+ Use `"standard_52_deck"` for standard playing cards. **Do NOT define 52 cards manually.**
66
+
67
+ ```json
68
+ {
69
+ "deckDefinitions": [
70
+ {
71
+ "type": "preset",
72
+ "id": "standard_52_deck",
73
+ "name": "Standard 52-Card Deck"
74
+ }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ #### Manual (custom) decks
80
+
81
+ Define your own cards with a `cardSchema` and a `cards` list.
82
+
83
+ ```json
84
+ {
85
+ "type": "manual",
86
+ "id": "resource-deck",
87
+ "name": "Resource Cards",
88
+ "cardSchema": {
89
+ "properties": {
90
+ "value": { "type": "integer", "description": "Point value of the card" },
91
+ "category": { "type": "string", "description": "Resource category" }
92
+ }
93
+ },
94
+ "cards": [
95
+ {
96
+ "type": "lumber",
97
+ "name": "Lumber",
98
+ "count": 4,
99
+ "properties": { "value": "1", "category": "wood" }
100
+ },
101
+ {
102
+ "type": "brick",
103
+ "name": "Brick",
104
+ "count": 3,
105
+ "properties": { "value": "2", "category": "stone" }
106
+ }
107
+ ]
108
+ }
109
+ ```
110
+
111
+ **Card fields:**
112
+
113
+ | Field | Required | Description |
114
+ | ------------ | -------- | ---------------------------------------------------------------------------------------- |
115
+ | `type` | ✅ | Card type ID. Runtime IDs are generated as `{type}-1`, `{type}-2`, etc. when `count > 1` |
116
+ | `name` | ✅ | Display name |
117
+ | `count` | ✅ | Number of copies (≥ 1) |
118
+ | `properties` | ✅ | Values matching `cardSchema` (all values are strings) |
119
+ | `imageUrl` | ❌ | Card image URL |
120
+ | `text` | ❌ | Text content on the card |
121
+ | `cardType` | ❌ | Optional category within the deck |
122
+
123
+ **Property schema types:** `string`, `integer`, `number`, `boolean`, `array`, `object`, `enum`, `deckId`, `cardId`, `playerId`
124
+
125
+ ### 3. `playerHandDefinitions`
126
+
127
+ Per-player card containers. Each player gets their own instance automatically.
128
+
129
+ ```json
130
+ {
131
+ "playerHandDefinitions": [
132
+ {
133
+ "id": "main-hand",
134
+ "displayName": "Hand",
135
+ "visibility": "ownerOnly",
136
+ "maxCards": 7,
137
+ "deckDefinitionIds": ["standard_52_deck"]
138
+ },
139
+ {
140
+ "id": "score-pile",
141
+ "displayName": "Scored Cards",
142
+ "visibility": "public"
143
+ }
144
+ ]
145
+ }
146
+ ```
147
+
148
+ | Field | Required | Default | Description |
149
+ | ------------------- | -------- | ------------- | ----------------------------------------------------------- |
150
+ | `id` | ✅ | — | Unique hand ID (becomes `HandId` type) |
151
+ | `displayName` | ✅ | — | UI label |
152
+ | `visibility` | ❌ | `"ownerOnly"` | `"ownerOnly"`, `"public"`, or `"hidden"` |
153
+ | `maxCards` | ❌ | — | Maximum cards allowed |
154
+ | `minCards` | ❌ | — | Minimum cards required |
155
+ | `deckDefinitionIds` | ❌ | — | Restrict to cards from these deck definitions (empty = any) |
156
+ | `description` | ❌ | — | Purpose description |
157
+
158
+ > **DECK vs HAND:** `DeckComponent` (in `components`) is **shared** — one instance per game (draw piles, discard piles, trick zones). `PlayerHandDefinition` is **per-player** — private hands, tableaus, collected cards.
159
+
160
+ ### 4. `components` — Shared Decks
161
+
162
+ Deck components are shared game zones that reference a deck definition.
163
+
164
+ ```json
165
+ {
166
+ "components": [
167
+ {
168
+ "type": "deck",
169
+ "id": "draw-pile",
170
+ "name": "Draw Pile",
171
+ "deckDefinitionId": "standard_52_deck",
172
+ "layout": "stack"
173
+ },
174
+ {
175
+ "type": "deck",
176
+ "id": "discard-pile",
177
+ "name": "Discard Pile",
178
+ "deckDefinitionId": "standard_52_deck",
179
+ "layout": "spread"
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ | Field | Required | Default | Description |
186
+ | ------------------ | -------- | --------- | --------------------------------------- |
187
+ | `type` | ✅ | — | `"deck"` |
188
+ | `id` | ✅ | — | Unique component ID |
189
+ | `name` | ✅ | — | Display name |
190
+ | `deckDefinitionId` | ✅ | — | Which deck definition this sources from |
191
+ | `layout` | ❌ | `"stack"` | `"stack"`, `"spread"`, or `"fan"` |
192
+
193
+ ### 5. `components` — Dice
194
+
195
+ Add dice as components with `type: "die"`.
196
+
197
+ ```json
198
+ {
199
+ "type": "die",
200
+ "id": "d6-die",
201
+ "name": "Six-Sided Die",
202
+ "sides": 6
203
+ }
204
+ ```
205
+
206
+ | Field | Required | Description |
207
+ | ------- | -------- | --------------------------------------------- |
208
+ | `type` | ✅ | `"die"` |
209
+ | `id` | ✅ | Unique die ID (e.g., `"d6-die"`, `"d20-die"`) |
210
+ | `name` | ✅ | Display name |
211
+ | `sides` | ✅ | Number of sides (≥ 2) |
212
+
213
+ ### 6. `resources` (Optional)
214
+
215
+ Typed resource economy for games with currencies or materials. Resources have a dedicated API (`canAfford`, `deduct`, `add`, `transfer`).
216
+
217
+ ```json
218
+ {
219
+ "resources": [
220
+ { "id": "gold", "name": "Gold" },
221
+ { "id": "wood", "name": "Wood" },
222
+ { "id": "victoryPoints", "name": "Victory Points" }
223
+ ]
224
+ }
225
+ ```
226
+
227
+ | Field | Required | Description |
228
+ | ------ | -------- | ------------------------------------------------------------------ |
229
+ | `id` | ✅ | Unique resource ID (alphanumeric + underscore, starts with letter) |
230
+ | `name` | ✅ | Display name |
231
+
232
+ > **Don't duplicate resources in `playerVariableSchema`.** Use the resource system instead.
233
+
234
+ ### 7. `boardDefinitions` (Optional)
235
+
236
+ For games with spatial structure. Each board type has its own shape:
237
+
238
+ | Board Type | Use Case | Key Concepts |
239
+ | ---------- | ------------------------------------- | ------------------------------------------------------------------------------------------------- |
240
+ | `hex` | Hexagonal grids (Catan) | Tiles (pre-defined with IDs/types), Edges `[TileId, TileId]`, Vertices `[TileId, TileId, TileId]` |
241
+ | `network` | Graph maps (Ticket to Ride, Pandemic) | Nodes (locations using TileId), Edges `[TileId, TileId]` |
242
+ | `square` | Grid boards (Chess, Checkers) | Cells derived from row/col (e.g., `"a1"` to `"h8"`), Pieces placed on cells |
243
+ | `track` | Path boards (Monopoly, VP track) | Sequential spaces with IDs, pieces on spaces |
244
+
245
+ ### 8. `availableActions`
246
+
247
+ Actions are **submissions** (buttons), **NOT selections**. Card/tile selection happens via UI clicks — the action definition declares the parameters that carry the selected items in the POST request.
248
+
249
+ ```json
250
+ {
251
+ "availableActions": [
252
+ {
253
+ "actionType": "playCard",
254
+ "displayName": "Play Card",
255
+ "description": "Play the selected card from your hand",
256
+ "parameters": [
257
+ {
258
+ "name": "cardId",
259
+ "type": "cardId",
260
+ "required": true,
261
+ "array": false,
262
+ "deckDefinitionId": "standard_52_deck"
263
+ }
264
+ ],
265
+ "errorCodes": ["NOT_YOUR_TURN", "INVALID_CARD", "MUST_FOLLOW_SUIT"]
266
+ },
267
+ {
268
+ "actionType": "pass",
269
+ "displayName": "Pass",
270
+ "parameters": [],
271
+ "errorCodes": ["CANNOT_PASS"]
272
+ },
273
+ {
274
+ "actionType": "discardCards",
275
+ "displayName": "Discard",
276
+ "parameters": [
277
+ {
278
+ "name": "cardIds",
279
+ "type": "cardId",
280
+ "required": true,
281
+ "array": true,
282
+ "minLength": 1,
283
+ "maxLength": 3,
284
+ "deckDefinitionId": "standard_52_deck"
285
+ }
286
+ ],
287
+ "errorCodes": ["WRONG_COUNT", "CARD_NOT_IN_HAND"]
288
+ }
289
+ ]
290
+ }
291
+ ```
292
+
293
+ **ActionDefinition fields:**
294
+
295
+ | Field | Required | Description |
296
+ | ------------- | -------- | ------------------------------------------------------------------------- |
297
+ | `actionType` | ✅ | Unique ID in camelCase (e.g., `"playCard"`, `"rollDice"`) |
298
+ | `displayName` | ✅ | Button label |
299
+ | `description` | ❌ | Help text |
300
+ | `parameters` | ✅ | List of parameters (can be empty for parameterless actions like `"pass"`) |
301
+ | `errorCodes` | ❌ | Possible validation error codes |
302
+
303
+ **ActionParameterDefinition fields:**
304
+
305
+ | Field | Required | Default | Description |
306
+ | ------------------ | -------- | ------- | ----------------------------------------------------- |
307
+ | `name` | ✅ | — | Parameter name |
308
+ | `type` | ✅ | — | See parameter types below |
309
+ | `required` | ❌ | `true` | Whether required |
310
+ | `array` | ❌ | `false` | Set `true` when multiple values can be sent |
311
+ | `minLength` | ❌ | — | Min items (only when `array: true`) |
312
+ | `maxLength` | ❌ | — | Max items (only when `array: true`) |
313
+ | `deckDefinitionId` | ❌ | — | Links `"cardId"` params to a specific deck definition |
314
+ | `description` | ❌ | — | Help text |
315
+
316
+ **Parameter types:**
317
+
318
+ | Type | Description |
319
+ | -------------- | ----------------------------------------------------------- |
320
+ | `"cardId"` | Runtime card instance ID (e.g., `"lumber-1"`, `"lumber-2"`) |
321
+ | `"cardType"` | Manifest-level card type identifier (e.g., `"lumber"`) |
322
+ | `"deckId"` | Deck component ID |
323
+ | `"playerId"` | Player ID |
324
+ | `"string"` | Free-form string |
325
+ | `"number"` | Numeric value |
326
+ | `"boolean"` | Boolean value |
327
+ | `"tileId"` | Hex tile or network node ID |
328
+ | `"edgeId"` | Edge between tiles/nodes |
329
+ | `"vertexId"` | Vertex between tiles |
330
+ | `"spaceId"` | Track board space ID |
331
+ | `"pieceId"` | Board piece ID |
332
+ | `"zoneId"` | Zone ID |
333
+ | `"tokenId"` | Token ID |
334
+ | `"resourceId"` | Resource ID |
335
+
336
+ **Key rules:**
337
+
338
+ - **NO `"selectCard"` actions.** UI clicks are not actions.
339
+ - If an action involves cards, **always include a `cardId` parameter** with the correct `deckDefinitionId`. Use `array: true` when multiple cards can be sent.
340
+ - Never use placeholder string params or empty parameter lists when the action consumes card data.
341
+ - Use `"cardId"` for specific card instances, `"cardType"` for card categories.
342
+ - Use camelCase for `actionType` names.
343
+ - Always use the specific type (e.g. deckId, cardId, playerId, etc) to narrow the parameter type instead of string where appropriate.
344
+
345
+ ### 9. `stateMachine`
346
+
347
+ Define game phases and transitions.
348
+
349
+ ```json
350
+ {
351
+ "stateMachine": {
352
+ "initialState": "dealCards",
353
+ "states": [
354
+ {
355
+ "name": "dealCards",
356
+ "type": "AUTO",
357
+ "description": "Shuffle the deck and deal 7 cards to each player.",
358
+ "transitions": [{ "targetState": "playCard" }],
359
+ "autoAdvance": true
360
+ },
361
+ {
362
+ "name": "playCard",
363
+ "type": "SINGLE_PLAYER",
364
+ "description": "Active player must play a valid card or draw from the pile.",
365
+ "availableActions": ["playCard", "drawCard"],
366
+ "transitions": [
367
+ { "targetState": "playCard", "description": "Next player's turn" },
368
+ {
369
+ "targetState": "gameOver",
370
+ "description": "Player has no cards left"
371
+ }
372
+ ],
373
+ "autoAdvance": true
374
+ },
375
+ {
376
+ "name": "gameOver",
377
+ "type": "AUTO",
378
+ "description": "Calculate final scores and determine the winner.",
379
+ "transitions": []
380
+ }
381
+ ]
382
+ }
383
+ }
384
+ ```
385
+
386
+ **State types:**
387
+
388
+ | Type | Description | Example |
389
+ | --------------- | ----------------------------------------------------------- | ------------------------------------------- |
390
+ | `SINGLE_PLAYER` | Engine waits for **one** player to act, then auto-advances | Chess turns, Poker betting |
391
+ | `ALL_PLAYERS` | Engine waits for **all** players to submit before advancing | Rock-Paper-Scissors, 7 Wonders card passing |
392
+ | `AUTO` | No player interaction — executes immediately | Dealing, scoring, state checks |
393
+
394
+ **StateDefinition fields:**
395
+
396
+ | Field | Required | Default | Description |
397
+ | ------------------ | -------- | ------- | ------------------------------------------------------------------------------- |
398
+ | `name` | ✅ | — | Unique state name in camelCase (use verbNoun format: `dealCards`, `playTurn`) |
399
+ | `type` | ✅ | — | `AUTO`, `SINGLE_PLAYER`, or `ALL_PLAYERS` |
400
+ | `description` | ✅ | — | Full logic description — what happens, what players can do |
401
+ | `availableActions` | ❌ | — | Action types available in this state (only for `SINGLE_PLAYER` / `ALL_PLAYERS`) |
402
+ | `transitions` | ✅ | — | List of possible next states |
403
+ | `autoAdvance` | ❌ | `true` | Whether to auto-advance when complete |
404
+
405
+ **StateTransition fields:**
406
+
407
+ | Field | Required | Description |
408
+ | ------------- | -------- | ------------------------------- |
409
+ | `targetState` | ✅ | Name of the next state |
410
+ | `description` | ❌ | When/why this transition occurs |
411
+
412
+ ### 10. `variableSchema`
413
+
414
+ Minimal state for game logic. Split into global (shared) and per-player variables.
415
+
416
+ ```json
417
+ {
418
+ "variableSchema": {
419
+ "globalVariableSchema": {
420
+ "properties": {
421
+ "currentRound": {
422
+ "type": "integer",
423
+ "description": "Current round number"
424
+ },
425
+ "trumpSuit": { "type": "string", "description": "Current trump suit" }
426
+ }
427
+ },
428
+ "playerVariableSchema": {
429
+ "properties": {
430
+ "score": { "type": "integer", "description": "Player's current score" },
431
+ "hasPassed": {
432
+ "type": "boolean",
433
+ "description": "Whether player has passed this round"
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+ ```
440
+
441
+ **Rules for variables:**
442
+
443
+ - **MINIMIZE state.** Only include what's needed for rules and logic.
444
+ - ✅ **Include:** scores, flags (`hasPassed`), logic blockers (`lastPlayedCards`), trump suit, round counters
445
+ - ❌ **Exclude:** derivable data (hand sizes, deck sizes, current player — the engine tracks these)
446
+ - ❌ **Don't duplicate resources** — use the `resources` section instead of player variables for economies
447
+ - Use `globalVariableSchema` for shared/global state (turn counter, current round)
448
+ - Use `playerVariableSchema` for per-player state (scores, flags)
449
+
450
+ **Property types:** `string`, `integer`, `number`, `boolean`, `array` (with `items`), `object` (with `properties`), `enum` (with `enums` list), `deckId`, `cardId`, `playerId`
451
+
452
+ ---
453
+
454
+ ## ID Naming Conventions
455
+
456
+ - Use **human-readable, kebab-case IDs** for components and hands: `"draw-pile"`, `"main-hand"`, `"d6-die"`
457
+ - Use **camelCase** for state names and action types: `"dealCards"`, `"playCard"`, `"rollDice"`
458
+ - Use **camelCase** for variable names: `"currentRound"`, `"hasPassed"`, `"trumpSuit"`
459
+ - Resource IDs: alphanumeric + underscore, starting with a letter: `"gold"`, `"victoryPoints"`
460
+
461
+ ---
462
+
463
+ ## Minimal Example
464
+
465
+ A simple draw-and-play card game for 2–4 players:
466
+
467
+ ```json
468
+ {
469
+ "version": "1.0.0",
470
+ "playerConfig": {
471
+ "minPlayers": 2,
472
+ "maxPlayers": 4,
473
+ "optimalPlayers": 3
474
+ },
475
+ "deckDefinitions": [
476
+ {
477
+ "type": "preset",
478
+ "id": "standard_52_deck",
479
+ "name": "Standard 52-Card Deck"
480
+ }
481
+ ],
482
+ "playerHandDefinitions": [
483
+ {
484
+ "id": "main-hand",
485
+ "displayName": "Hand",
486
+ "visibility": "ownerOnly",
487
+ "maxCards": 7,
488
+ "deckDefinitionIds": ["standard_52_deck"]
489
+ }
490
+ ],
491
+ "components": [
492
+ {
493
+ "type": "deck",
494
+ "id": "draw-pile",
495
+ "name": "Draw Pile",
496
+ "deckDefinitionId": "standard_52_deck",
497
+ "layout": "stack"
498
+ },
499
+ {
500
+ "type": "deck",
501
+ "id": "discard-pile",
502
+ "name": "Discard Pile",
503
+ "deckDefinitionId": "standard_52_deck",
504
+ "layout": "spread"
505
+ }
506
+ ],
507
+ "availableActions": [
508
+ {
509
+ "actionType": "playCard",
510
+ "displayName": "Play Card",
511
+ "description": "Play a card from your hand to the discard pile",
512
+ "parameters": [
513
+ {
514
+ "name": "cardId",
515
+ "type": "cardId",
516
+ "required": true,
517
+ "deckDefinitionId": "standard_52_deck"
518
+ }
519
+ ],
520
+ "errorCodes": ["NOT_YOUR_TURN", "INVALID_PLAY"]
521
+ },
522
+ {
523
+ "actionType": "drawCard",
524
+ "displayName": "Draw Card",
525
+ "description": "Draw a card from the draw pile",
526
+ "parameters": [],
527
+ "errorCodes": ["HAND_FULL", "DECK_EMPTY"]
528
+ }
529
+ ],
530
+ "stateMachine": {
531
+ "initialState": "dealCards",
532
+ "states": [
533
+ {
534
+ "name": "dealCards",
535
+ "type": "AUTO",
536
+ "description": "Shuffle the deck and deal 5 cards to each player. Place remaining cards face-down as the draw pile. Flip the top card to start the discard pile.",
537
+ "transitions": [{ "targetState": "playTurn" }]
538
+ },
539
+ {
540
+ "name": "playTurn",
541
+ "type": "SINGLE_PLAYER",
542
+ "description": "Active player must play a matching card from their hand or draw from the draw pile. A card matches if it shares the same suit or rank as the top discard.",
543
+ "availableActions": ["playCard", "drawCard"],
544
+ "transitions": [
545
+ { "targetState": "playTurn", "description": "Next player's turn" },
546
+ {
547
+ "targetState": "endRound",
548
+ "description": "Player empties their hand"
549
+ }
550
+ ]
551
+ },
552
+ {
553
+ "name": "endRound",
554
+ "type": "AUTO",
555
+ "description": "The player who emptied their hand wins. Calculate scores based on cards remaining in other players' hands.",
556
+ "transitions": []
557
+ }
558
+ ]
559
+ },
560
+ "variableSchema": {
561
+ "globalVariableSchema": {
562
+ "properties": {}
563
+ },
564
+ "playerVariableSchema": {
565
+ "properties": {
566
+ "score": {
567
+ "type": "integer",
568
+ "description": "Accumulated score across rounds"
569
+ }
570
+ }
571
+ }
572
+ }
573
+ }
574
+ ```
575
+
576
+ ---
577
+
578
+ ## After Editing
579
+
580
+ Run `dreamboard update` to push the manifest and regenerate scaffolded files:
581
+
582
+ ```bash
583
+ dreamboard update
584
+ ```
585
+
586
+ This regenerates:
587
+
588
+ - `app/phases/` — One handler file per state in the state machine
589
+ - `shared/manifest.d.ts` — TypeScript type definitions derived from the manifest
590
+ - Action handler stubs and variable type definitions