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