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,193 @@
|
|
|
1
|
+
# State & API Reference
|
|
2
|
+
|
|
3
|
+
## Read APIs (`ctx.state.*`)
|
|
4
|
+
|
|
5
|
+
### `state.player`
|
|
6
|
+
|
|
7
|
+
| Method | Returns | Description |
|
|
8
|
+
| -------------------------------------- | ------------- | ------------------------------------- |
|
|
9
|
+
| `.getOrder()` | `PlayerId[]` | All players in turn order |
|
|
10
|
+
| `.getCurrentIds()` | `PlayerId[]` | Currently active player IDs |
|
|
11
|
+
| `.get(playerId)` | `Player` | Player data: `{ id, name, score }` |
|
|
12
|
+
| `.getState(playerId)` | `PlayerState` | Player variables from manifest schema |
|
|
13
|
+
| `.getHand(playerId, handId)` | `CardId[]` | Cards in a specific hand |
|
|
14
|
+
| `.getAllHands(playerId)` | `CardId[]` | All cards across all hands |
|
|
15
|
+
| `.isInHand(playerId, cardId, handId?)` | `boolean` | Check if card is in hand |
|
|
16
|
+
| `.isActive(playerId)` | `boolean` | Is player currently active |
|
|
17
|
+
|
|
18
|
+
### `state.deck`
|
|
19
|
+
|
|
20
|
+
| Method | Returns | Description |
|
|
21
|
+
| --------------------- | ---------------- | ---------------------- |
|
|
22
|
+
| `.getCards(deckId)` | `CardId[]` | Cards in a shared deck |
|
|
23
|
+
| `.getTopCard(deckId)` | `CardId \| null` | Top card of deck |
|
|
24
|
+
|
|
25
|
+
### `state.card`
|
|
26
|
+
|
|
27
|
+
| Method | Returns | Description |
|
|
28
|
+
| -------------------------------- | ------------------ | ---------------------------------------------------- |
|
|
29
|
+
| `.get(cardId)` | `Card` | Full card data |
|
|
30
|
+
| `.getProperties(cardId)` | Typed properties | Card-specific props (rank, suit, etc.) |
|
|
31
|
+
| `.getLocation(cardId)` | `Location` | Where the card is (InHand, InDeck, InZone, Detached) |
|
|
32
|
+
| `.getPlayedBy(cardId)` | `PlayerId \| null` | Who played this card to a deck/zone |
|
|
33
|
+
| `.getOwner(cardId)` | `PlayerId \| null` | Card owner |
|
|
34
|
+
| `.isVisibleTo(cardId, playerId)` | `boolean` | Visibility check |
|
|
35
|
+
| `.getOwnedBy(playerId)` | `CardId[]` | All cards owned by player |
|
|
36
|
+
|
|
37
|
+
### `state.game`
|
|
38
|
+
|
|
39
|
+
| Method | Returns | Description |
|
|
40
|
+
| -------------------- | ------------- | ------------------------------------- |
|
|
41
|
+
| `.getGlobalState()` | `GlobalState` | Global variables from manifest schema |
|
|
42
|
+
| `.getCurrentState()` | `StateName` | Current state machine state |
|
|
43
|
+
|
|
44
|
+
## Mutation APIs (`ctx.apis.*`)
|
|
45
|
+
|
|
46
|
+
### `apis.cardApi`
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
| --------------------------------------------------------------------------- | ------------------------------ |
|
|
50
|
+
| `.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId)` | Hand → Deck |
|
|
51
|
+
| `.moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` | Hand → Hand |
|
|
52
|
+
| `.moveCardsToPlayer(cardIds, toPlayerId, handId)` | Any → Hand |
|
|
53
|
+
| `.flip(deckId, cardId)` | Flip card face up/down |
|
|
54
|
+
| `.detachCard(cardId)` | Remove card from all locations |
|
|
55
|
+
| `.transferOwnership(cardId, toPlayer)` | Change card owner |
|
|
56
|
+
|
|
57
|
+
#### Card movement semantics
|
|
58
|
+
|
|
59
|
+
- `moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` is **additive** at destination.
|
|
60
|
+
- Destination cards are preserved; moved cards are added to the destination hand.
|
|
61
|
+
- This API does **not** replace/overwrite the destination hand.
|
|
62
|
+
- For pass/rotate mechanics where each player should receive exactly one other player's hand, avoid in-place cyclic moves. Snapshot source hands first, then move via a temporary location (or other two-phase transfer pattern).
|
|
63
|
+
|
|
64
|
+
### `apis.deckApi`
|
|
65
|
+
|
|
66
|
+
| Method | Description |
|
|
67
|
+
| ------------------------------------------------------------- | ------------------------- |
|
|
68
|
+
| `.moveCardsFromDeckToPlayer(deckId, playerId, handId, count)` | Deck → Hand (deal) |
|
|
69
|
+
| `.moveCardsFromDeckToDeck(fromDeckId, toDeckId)` | Deck → Deck |
|
|
70
|
+
| `.shuffle(deckId)` | Shuffle a deck |
|
|
71
|
+
| `.addCards(deckId, cardIds)` | Add cards to a deck |
|
|
72
|
+
| `.removeCard(deckId, cardId)` | Remove a card from a deck |
|
|
73
|
+
|
|
74
|
+
### `apis.gameApi`
|
|
75
|
+
|
|
76
|
+
| Method | Description |
|
|
77
|
+
| ---------------------------------- | ---------------------------------------------- |
|
|
78
|
+
| `.setActivePlayers(playerIds)` | Set who can act (use in ALL_PLAYERS `onEnter`) |
|
|
79
|
+
| `.setNextPlayer(playerId)` | Set single active player |
|
|
80
|
+
| `.advanceTurn()` | Move to next player in order |
|
|
81
|
+
| `.declareWinner(playerId, reason)` | Declare game winner |
|
|
82
|
+
| `.endGame()` | End the game |
|
|
83
|
+
|
|
84
|
+
### `apis.globalStateApi`
|
|
85
|
+
|
|
86
|
+
| Method | Description |
|
|
87
|
+
| ------------------------------------- | ---------------------------- |
|
|
88
|
+
| `.setGlobalState(newState)` | Replace all global variables |
|
|
89
|
+
| `.setPlayerState(playerId, newState)` | Replace a player's variables |
|
|
90
|
+
|
|
91
|
+
### `apis.kvApi`
|
|
92
|
+
|
|
93
|
+
Internal-only key-value store. **UI cannot access these values.**
|
|
94
|
+
|
|
95
|
+
| Method | Returns | Description |
|
|
96
|
+
| ------------------ | ---------------- | ----------------------------------- |
|
|
97
|
+
| `.set(key, value)` | `KvSetResult` | Store a JSON value |
|
|
98
|
+
| `.get(key)` | `KvGetResult` | Read a value (`.success`, `.value`) |
|
|
99
|
+
| `.delete(key)` | `KvDeleteResult` | Remove a key |
|
|
100
|
+
| `.has(key)` | `boolean` | Check existence |
|
|
101
|
+
| `.keys()` | `string[]` | List all keys |
|
|
102
|
+
|
|
103
|
+
### `apis.resourceApi`
|
|
104
|
+
|
|
105
|
+
| Method | Description |
|
|
106
|
+
| ------------------------- | -------------------------- |
|
|
107
|
+
| `.add(playerId, cost)` | Give resources to player |
|
|
108
|
+
| `.deduct(playerId, cost)` | Take resources from player |
|
|
109
|
+
|
|
110
|
+
### `apis.dieApi`
|
|
111
|
+
|
|
112
|
+
| Method | Description |
|
|
113
|
+
| -------------------------- | ------------------------- |
|
|
114
|
+
| `.roll(dieId)` | Roll a die |
|
|
115
|
+
| `.setValue(dieId, value?)` | Set die to specific value |
|
|
116
|
+
|
|
117
|
+
## Typed KV Store
|
|
118
|
+
|
|
119
|
+
Use `createTypedKv` from `sdk/stateApi.js` for type-safe KV access:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createTypedKv } from "../sdk/stateApi.js";
|
|
123
|
+
|
|
124
|
+
interface MyKv {
|
|
125
|
+
playersActed: PlayerId[];
|
|
126
|
+
roundData: { scores: number[] };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const kv = createTypedKv<MyKv>(apis.kvApi);
|
|
130
|
+
kv.set("playersActed", ["player-1"]); // Type-checked key and value
|
|
131
|
+
const acted = kv.get("playersActed"); // PlayerId[] | null
|
|
132
|
+
kv.has("playersActed"); // boolean
|
|
133
|
+
kv.delete("playersActed"); // boolean (existed)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Card `playedBy` Tracking
|
|
137
|
+
|
|
138
|
+
When cards move from a Hand to a Deck via `moveCardsFromHandToDeck()`, the engine automatically records `playedBy` on each card:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Move card from player's hand to a shared deck
|
|
142
|
+
apis.cardApi.moveCardsFromHandToDeck(
|
|
143
|
+
playerId,
|
|
144
|
+
"main-hand",
|
|
145
|
+
[cardId],
|
|
146
|
+
"play-area",
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Later, check who played a specific card
|
|
150
|
+
const whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Location Type
|
|
154
|
+
|
|
155
|
+
Cards have a location discriminated union:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
type Location =
|
|
159
|
+
| { type: "Detached" }
|
|
160
|
+
| {
|
|
161
|
+
type: "InDeck";
|
|
162
|
+
deckId: DeckId;
|
|
163
|
+
playedBy: PlayerId | null;
|
|
164
|
+
position: number | null;
|
|
165
|
+
}
|
|
166
|
+
| {
|
|
167
|
+
type: "InHand";
|
|
168
|
+
handId: HandId;
|
|
169
|
+
playerId: PlayerId;
|
|
170
|
+
position: number | null;
|
|
171
|
+
}
|
|
172
|
+
| {
|
|
173
|
+
type: "InZone";
|
|
174
|
+
zoneId: string;
|
|
175
|
+
playedBy: PlayerId | null;
|
|
176
|
+
position: number | null;
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Validation Helpers
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { validationSuccess, validationError } from "../sdk/validation.js";
|
|
184
|
+
|
|
185
|
+
// Valid action
|
|
186
|
+
return validationSuccess();
|
|
187
|
+
|
|
188
|
+
// Invalid action — errorCode should be kebab-case
|
|
189
|
+
return validationError(
|
|
190
|
+
"must-play-valid-combination",
|
|
191
|
+
"You must play a valid card combination",
|
|
192
|
+
);
|
|
193
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# App Best Practices
|
|
2
|
+
|
|
3
|
+
Best practices for working on `app/phases/*.ts` — the server-side game logic.
|
|
4
|
+
|
|
5
|
+
## Before writing any code
|
|
6
|
+
|
|
7
|
+
1. **Read the manifest** — understand all decks, hands, zones, resources, state machine states, and card types from `shared/manifest.d.ts`.
|
|
8
|
+
2. **Read all existing phases** — read every file in `app/phases/` to understand the current flow.
|
|
9
|
+
3. **Read the types** — review `app/sdk/types.d.ts`, `app/sdk/phaseHandlers.ts`, and `app/sdk/state.d.ts` for the full API surface.
|
|
10
|
+
|
|
11
|
+
## Core principles
|
|
12
|
+
|
|
13
|
+
1. **Authoritative script** — the app script is the source of truth for a multiplayer game. All mutations must go through APIs; never assume the UI tracks state.
|
|
14
|
+
2. **Optimistic results** — use the data returned by mutation APIs within the same transaction. `state.*` reads may be stale until the next call.
|
|
15
|
+
3. **Active vs next players** — use `setActivePlayers()` for simultaneous phases (ALL_PLAYERS); use `setNextPlayer()` for turn-based phases (SINGLE_PLAYER).
|
|
16
|
+
4. **Card movement guardrail** — every card location change must use a move API. Never assume the UI or staging moved cards.
|
|
17
|
+
5. **Manifest alignment** — use deck/hand/zone IDs exactly as defined in `shared/manifest.d.ts`. Do not invent new identifiers.
|
|
18
|
+
6. **KV is internal** — `kvApi` is invisible to the UI. Pass data to UI via `getUIArgs()`, `globalStateApi`, or `playerStateApi`.
|
|
19
|
+
7. **Preserve player-visible outcomes** — if an AUTO phase resolves a round or a reveal, keep the round-result information in state long enough for the next player-visible phase to render it.
|
|
20
|
+
|
|
21
|
+
## Validation
|
|
22
|
+
|
|
23
|
+
- Always implement `validateAction` for SINGLE_PLAYER and ALL_PLAYERS phases.
|
|
24
|
+
- Return `validationSuccess()` for valid actions.
|
|
25
|
+
- Return `validationError(code, message)` for invalid actions. Codes should be kebab-case (e.g., `'must-play-valid-combination'`).
|
|
26
|
+
|
|
27
|
+
## UIArgs contract
|
|
28
|
+
|
|
29
|
+
- Define UIArgs interfaces in `shared/ui-args.ts` to specify what data flows from app to UI.
|
|
30
|
+
- Implement `getUIArgs(ctx, playerId)` in each player-facing phase to return the data the UI needs.
|
|
31
|
+
- UIArgs should be minimal — only pass what the UI actually uses.
|
|
32
|
+
- Avoid passing raw card IDs when you can pass computed values (e.g., `canPlayCard: boolean` instead of making the UI recompute).
|
|
33
|
+
|
|
34
|
+
## Array and type safety
|
|
35
|
+
|
|
36
|
+
- Check array length before indexing. Never assume arrays are non-empty.
|
|
37
|
+
- Use guards from `app/generated/guards.ts` via `../generated/guards` for ID type assertions.
|
|
38
|
+
- Never cast to `any` — always work with the generated types.
|
|
39
|
+
|
|
40
|
+
## Logging
|
|
41
|
+
|
|
42
|
+
- Log key actions with concise payloads (IDs and counts), not full arrays.
|
|
43
|
+
- Use `ctx.logger` for structured logging.
|
|
44
|
+
|
|
45
|
+
## Common patterns
|
|
46
|
+
|
|
47
|
+
### Typed KV store
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { createTypedKv } from "../sdk/stateApi.js";
|
|
51
|
+
|
|
52
|
+
interface PhaseKv {
|
|
53
|
+
playersActed: PlayerId[];
|
|
54
|
+
roundNumber: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const kv = createTypedKv<PhaseKv>(apis.kvApi);
|
|
58
|
+
kv.set("playersActed", []);
|
|
59
|
+
const acted = kv.get("playersActed"); // PlayerId[] | null
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Card playedBy tracking
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Cards moved via moveCardsFromHandToDeck automatically track playedBy
|
|
66
|
+
apis.cardApi.moveCardsFromHandToDeck(
|
|
67
|
+
playerId,
|
|
68
|
+
"main-hand",
|
|
69
|
+
[cardId],
|
|
70
|
+
"play-area",
|
|
71
|
+
);
|
|
72
|
+
const whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Turn advancement
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// In onAfterAction for SINGLE_PLAYER phases
|
|
79
|
+
onAfterAction(ctx, playerId, actionType) {
|
|
80
|
+
ctx.apis.gameApi.advanceTurn();
|
|
81
|
+
},
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### ALL_PLAYERS tracking
|
|
85
|
+
|
|
86
|
+
See [all-players-tracking.md](all-players-tracking.md) for the recommended `ctx.phase` tracking pattern.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Hands vs Decks
|
|
2
|
+
|
|
3
|
+
The framework distinguishes between **Hands** and **Decks**. Confusing them causes silent bugs.
|
|
4
|
+
|
|
5
|
+
## Definitions
|
|
6
|
+
|
|
7
|
+
| Concept | Manifest key | Type ID | Access API | Description |
|
|
8
|
+
| -------- | ----------------------------- | -------- | ---------------------------------------- | ------------------------------------------------------------ |
|
|
9
|
+
| **Hand** | `playerHandDefinitions` | `HandId` | `state.player.getHand(playerId, handId)` | Cards owned by a specific player. Per-player. |
|
|
10
|
+
| **Deck** | `components` (type: `"deck"`) | `DeckId` | `state.deck.getCards(deckId)` | Shared card piles (draw pile, discard, battle zone). Global. |
|
|
11
|
+
|
|
12
|
+
## Key rule
|
|
13
|
+
|
|
14
|
+
A player's collection of cards is always a **Hand** (`HandId`), even if the real-world game calls it a "deck."
|
|
15
|
+
|
|
16
|
+
For example, in the War card game, each player's face-down pile is called a "deck" in real life, but in the framework it's defined as a **Hand** with id `"player-deck"` under `playerHandDefinitions`.
|
|
17
|
+
|
|
18
|
+
## Manifest Example (War card game)
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"components": [
|
|
23
|
+
{ "type": "deck", "id": "main-deck", "name": "Main Deck" },
|
|
24
|
+
{ "type": "deck", "id": "battle-zone", "name": "Battle Zone" },
|
|
25
|
+
{ "type": "deck", "id": "war-pile", "name": "War Pile" }
|
|
26
|
+
],
|
|
27
|
+
"playerHandDefinitions": [
|
|
28
|
+
{
|
|
29
|
+
"id": "player-deck",
|
|
30
|
+
"displayName": "Your Deck",
|
|
31
|
+
"visibility": "ownerOnly"
|
|
32
|
+
},
|
|
33
|
+
{ "id": "won-pile", "displayName": "Won Cards", "visibility": "ownerOnly" }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `"player-deck"` → **HandId** (per-player) → `state.player.getHand(playerId, "player-deck")`
|
|
39
|
+
- `"battle-zone"` → **DeckId** (shared) → `state.deck.getCards("battle-zone")`
|
|
40
|
+
- `"war-pile"` → **DeckId** (shared) → `state.deck.getCards("war-pile")`
|
|
41
|
+
|
|
42
|
+
## Moving Cards
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Hand → Deck (player plays card to shared area)
|
|
46
|
+
apis.cardApi.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId);
|
|
47
|
+
|
|
48
|
+
// Deck → Hand (deal from shared pile to player)
|
|
49
|
+
apis.deckApi.moveCardsFromDeckToPlayer(deckId, playerId, handId, count);
|
|
50
|
+
|
|
51
|
+
// Hand → Hand (pass cards between players)
|
|
52
|
+
apis.cardApi.moveCardsFromHandToHand(
|
|
53
|
+
fromPlayer,
|
|
54
|
+
fromHand,
|
|
55
|
+
toPlayer,
|
|
56
|
+
toHand,
|
|
57
|
+
cardIds,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Deck → Deck (move all cards between shared piles)
|
|
61
|
+
apis.deckApi.moveCardsFromDeckToDeck(fromDeckId, toDeckId);
|
|
62
|
+
|
|
63
|
+
// Any card → Hand (move specific cards to a player)
|
|
64
|
+
apis.cardApi.moveCardsToPlayer(cardIds, toPlayerId, handId);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Hand Passing: Anti-Pattern vs Safe Pattern
|
|
68
|
+
|
|
69
|
+
`moveCardsFromHandToHand` appends to destination. That makes in-place cyclic passing unsafe.
|
|
70
|
+
|
|
71
|
+
### Anti-pattern (in-place cyclic pass)
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
for (let i = 0; i < order.length; i++) {
|
|
75
|
+
const fromPlayer = order[i];
|
|
76
|
+
const toPlayer = order[(i + 1) % order.length];
|
|
77
|
+
const cardIds = state.player.getHand(fromPlayer, "hand");
|
|
78
|
+
apis.cardApi.moveCardsFromHandToHand(
|
|
79
|
+
fromPlayer,
|
|
80
|
+
"hand",
|
|
81
|
+
toPlayer,
|
|
82
|
+
"hand",
|
|
83
|
+
cardIds,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|