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.
- package/README.md +239 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-FK6CWXQR.js +3479 -0
- package/dist/chunk-FK6CWXQR.js.map +1 -0
- package/dist/chunk-MP7IBNWW.js +13289 -0
- package/dist/chunk-MP7IBNWW.js.map +1 -0
- package/dist/dist-B3R64F6G.js +99 -0
- package/dist/dist-B3R64F6G.js.map +1 -0
- package/dist/embedded-harness-PF2LCIWC.js +345 -0
- package/dist/embedded-harness-PF2LCIWC.js.map +1 -0
- package/dist/index.js +25773 -0
- package/dist/index.js.map +1 -0
- package/dist/prompt-GMZABCJC.js +756 -0
- package/dist/prompt-GMZABCJC.js.map +1 -0
- package/package.json +40 -0
- package/skills/dreamboard/SKILL.md +119 -0
- package/skills/dreamboard/references/adversarial-testing.md +113 -0
- package/skills/dreamboard/references/all-players-tracking.md +75 -0
- package/skills/dreamboard/references/api-reference.md +193 -0
- package/skills/dreamboard/references/app-best-practices.md +86 -0
- package/skills/dreamboard/references/hands-vs-decks.md +86 -0
- package/skills/dreamboard/references/manifest-authoring.md +590 -0
- package/skills/dreamboard/references/phase-handlers.md +134 -0
- package/skills/dreamboard/references/rule-authoring.md +142 -0
- package/skills/dreamboard/references/scenario-format.md +99 -0
- package/skills/dreamboard/references/test-harness.md +225 -0
- package/skills/dreamboard/references/tts-migration-and-extractor.md +91 -0
- package/skills/dreamboard/references/ui-best-practices.md +158 -0
- package/skills/dreamboard/references/ui-genre-resource-management.md +187 -0
- package/skills/dreamboard/references/ui-genre-trick-taking.md +110 -0
- package/skills/dreamboard/references/ui-genre-worker-placement.md +143 -0
- package/skills/dreamboard/references/ui-style-guide.md +54 -0
- package/skills/dreamboard/scripts/events-extract.mjs +218 -0
- 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
|