@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +36 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27368 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16884 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +209 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +575 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +335 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +122 -3
@@ -0,0 +1,116 @@
1
+ # Create a session UI
2
+
3
+ Build the facilitator's screen: every team's state side by side, with controls to change values and run the session.
4
+
5
+ **Keywords:** manage session, facilitator UI, facilitator dashboard, session overview, all teams, session user interface, admin UI, admin panel, facilitator panel, hidden from participants, facilitator-only, game master, instructor view, cross-team
6
+
7
+ ## Prerequisites
8
+
9
+ - Capabilities to show and change (here `price`, `workers`, and `production`); see [Create a capability](create-capability.md)
10
+ - `seriousgaming.sdk.json` in your working directory
11
+
12
+ ## Steps
13
+
14
+ 1. Declare what the session UI reads and writes. A game holds at most one, in the `session/` slot, and the starter game already fills it, so in a package made with `new package` update it:
15
+
16
+ ```bash
17
+ pnpm run sdk update session --patch '[
18
+ { "op": "replace", "path": "/definition/reads", "value": ["production"] },
19
+ { "op": "replace", "path": "/definition/writes", "value": ["price", "workers"] }
20
+ ]'
21
+ ```
22
+
23
+ In a package without one (made with `new package --empty`), create it:
24
+
25
+ ```bash
26
+ pnpm run sdk new session '{
27
+ "definition": {
28
+ "reads": ["production"],
29
+ "writes": ["price", "workers"]
30
+ },
31
+ "rootDirectory": ".",
32
+ "ui": { "entrypoint": "index.html" }
33
+ }'
34
+ ```
35
+
36
+ Either regenerates `session/types.ts`, which includes a ready-made `SessionData`.
37
+
38
+ 2. Write the overview in `session/src/App.tsx`. `setData` takes the team id first, so each change lands on the team whose card it came from:
39
+
40
+ ```tsx
41
+ import {
42
+ useSessionUserInterfaceCallbacks,
43
+ useSessionUserInterfaceData,
44
+ useSessionUserInterfaceTeams,
45
+ } from '@seriousgaming/sdk/ui';
46
+
47
+ import type { SessionData, Settings } from '../types';
48
+
49
+ export default function App() {
50
+ const { sessionData, setData } = useSessionUserInterfaceData<SessionData, Settings>();
51
+ const teams = useSessionUserInterfaceTeams();
52
+ const { requestTick } = useSessionUserInterfaceCallbacks();
53
+
54
+ return (
55
+ <main>
56
+ {teams.map(({ teamId, name }) => {
57
+ const team = sessionData.teams[teamId];
58
+ if (!team) return null;
59
+ return (
60
+ <section key={teamId}>
61
+ <h2>{name}</h2>
62
+ <p>Production: {team.context.production.Production}</p>
63
+ <label>
64
+ Price
65
+ <input
66
+ type="number"
67
+ value={team.context.price.Price}
68
+ onChange={(e) =>
69
+ setData(teamId, {
70
+ price: { Price: Number(e.target.value) },
71
+ })
72
+ }
73
+ />
74
+ </label>
75
+ <button onClick={() => requestTick(teamId)}>Tick</button>
76
+ </section>
77
+ );
78
+ })}
79
+ </main>
80
+ );
81
+ }
82
+ ```
83
+
84
+ A `scope: 'shared'` capability arrives once, in `sessionData.shared.context`, and is written with the one-argument `setData(update)`. See [Writing shared capabilities](../guides/multi-team-games.md#facilitator-writable-shared-capabilities) and the [session UI hooks reference](../guides/session.md#hooks-reference) for roster control, timers, undo, and reset.
85
+
86
+ 3. If you replaced the starter's session UI, delete its other components and tests under `session/src/`, which read capabilities this one no longer declares.
87
+
88
+ 4. Build:
89
+
90
+ ```bash
91
+ pnpm run build
92
+ ```
93
+
94
+ ## Verify
95
+
96
+ ```bash
97
+ pnpm run sdk dev
98
+ ```
99
+
100
+ The session UI is the console at http://localhost:3000. Change a price on one team's card, then open that team's participant view: it shows the new value.
101
+
102
+ ## Troubleshooting
103
+
104
+ | Problem | Solution |
105
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
106
+ | `new session` fails with `RESOURCE_EXISTS` | The slot is filled: use `update session` (step 1), or `pnpm run sdk delete session --yes` and then `new session` |
107
+ | The teams list is empty | The session has no teams yet; `useSessionUserInterfaceTeams` returns `[]` until it does |
108
+ | A field shows a stale value | Use `value=` (controlled), not `defaultValue=` |
109
+ | A change lands on the wrong team | Check the first argument to `setData` is that card's `teamId` |
110
+ | A shared write is refused | The capability must be `scope: 'shared'` and in this UI's `writes`; a per-team capability uses `setData(teamId, update)` |
111
+
112
+ ## Related
113
+
114
+ - [Session UI guide](../guides/session.md)
115
+ - [Create a user interface](create-user-interface.md)
116
+ - [Test a user interface](test-user-interface.md)
@@ -0,0 +1,88 @@
1
+ # Create settings
2
+
3
+ Give a game a read-only parameter, such as a price, a difficulty, or an answer key, that its logic and screens read and that scenarios and facilitators can change before play starts.
4
+
5
+ **Keywords:** create settings, new settings, settings resource, settingsSchema, game parameters, configuration, configurable value, scenario override, answer key, difficulty
6
+
7
+ ## Prerequisites
8
+
9
+ - A package with `seriousgaming.sdk.json` in your working directory
10
+
11
+ ## Steps
12
+
13
+ 1. Create the settings resource. Every property needs a `default`, and the schema has no top-level `required`. Describe each property (`title`, `description` with units, `minimum` and `maximum`): editors and AI agents read the schema:
14
+
15
+ ```bash
16
+ pnpm run sdk new settings '{
17
+ "definition": {
18
+ "identifier": "demand",
19
+ "settingsSchema": {
20
+ "type": "object",
21
+ "properties": {
22
+ "BaseDemand": {
23
+ "type": "number",
24
+ "title": "Base demand",
25
+ "description": "Units customers would buy each tick at a price of zero",
26
+ "default": 100,
27
+ "minimum": 0,
28
+ "maximum": 10000
29
+ }
30
+ },
31
+ "additionalProperties": false
32
+ }
33
+ }
34
+ }'
35
+ ```
36
+
37
+ 2. List it on each resource that reads it. For the engine and the game UI:
38
+
39
+ ```bash
40
+ pnpm run sdk update engine --patch '[{ "op": "add", "path": "/definition/settings/-", "value": "demand" }]'
41
+ pnpm run sdk update ui --patch '[{ "op": "add", "path": "/definition/settings/-", "value": "demand" }]'
42
+ ```
43
+
44
+ Use `"path": "/definition/settings", "value": ["demand"]` for a resource that lists no settings yet. Each command regenerates that resource's `types.ts`.
45
+
46
+ 3. Read it in logic as `settings.demand`:
47
+
48
+ ```typescript
49
+ export const Run: RunFunction<Context, Settings> = async ({ context, settings }) => {
50
+ const Sales = Math.max(0, settings.demand.BaseDemand - context.price.Price * 2);
51
+ return { capabilities: { ...context, sales: { ...context.sales, Sales } } };
52
+ };
53
+ ```
54
+
55
+ 4. Read it in a screen from `useUserInterfaceData()`:
56
+
57
+ ```tsx
58
+ const { settings } = useUserInterfaceData<Context, Settings>();
59
+ return <p>Typical demand: {settings.demand.BaseDemand}</p>;
60
+ ```
61
+
62
+ 5. Optional: override it in a scenario, keyed by settings identifier (see [Create a scenario](create-scenario.md)). To change it for every session, edit its `default`.
63
+
64
+ ## Verify
65
+
66
+ ```bash
67
+ pnpm run sdk configure '{"demand":{"BaseDemand":250}}'
68
+ pnpm run sdk view --full
69
+ ```
70
+
71
+ The resolved `settings.demand.BaseDemand` is `250`, and the next `pnpm run sdk tick` plays with it.
72
+
73
+ ## Troubleshooting
74
+
75
+ | Problem | Solution |
76
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
77
+ | `Every settings property declares a default, and "X" does not` | Add a `default` to that property |
78
+ | `reads settings "demand", which this package does not declare` | The directory name and the `settings` entry differ |
79
+ | `settings.demand` is undefined | Add `demand` to that resource's `settings` |
80
+ | `Unknown settings "engine"` from `configure` or a scenario | Overrides are keyed by settings identifier, not by the resource that reads them |
81
+ | A participant can see a value they should not | Move it to a settings resource only the engine or an endpoint lists |
82
+ | The value does not change mid-game | Expected: settings are fixed when play starts. Use a capability for anything that changes during play |
83
+
84
+ ## Related
85
+
86
+ - [Settings guide](../guides/settings.md): declaring, reading, overriding, and validation
87
+ - [Create a scenario](create-scenario.md)
88
+ - [Create a setup screen](create-setup.md)
@@ -0,0 +1,115 @@
1
+ # Create a setup screen
2
+
3
+ Give facilitators a short form at session creation that sets a few of your game's settings for that session only.
4
+
5
+ **Keywords:** setup, setup screen, quick config, session setup, configuration step, configure session, settings form, requestPlay, configures
6
+
7
+ ## What you'll build
8
+
9
+ A setup screen at `setup/` that edits the `engine` settings and starts play from its own button. A game with a `setup` slot opens at the configuration step: nothing runs until the facilitator presses Play. See the [setup guide](../guides/setup.md) for the full behaviour.
10
+
11
+ ## Prerequisites
12
+
13
+ - A settings resource for the screen to edit; see [Create a settings resource](create-settings.md)
14
+ - `seriousgaming.sdk.json` in your working directory
15
+
16
+ ## Steps
17
+
18
+ ### 1. Create the slot
19
+
20
+ ```bash
21
+ pnpm run sdk new setup '{
22
+ "definition": {
23
+ "configures": ["engine"]
24
+ },
25
+ "rootDirectory": ".",
26
+ "ui": { "entrypoint": "index.html" }
27
+ }'
28
+ ```
29
+
30
+ This creates:
31
+
32
+ ```
33
+ setup/
34
+ ├── setup.sdk.json
35
+ ├── defaults.ts
36
+ ├── index.html
37
+ ├── types.ts
38
+ └── src/
39
+ ├── main.tsx
40
+ ├── App.tsx
41
+ ├── App.test.tsx
42
+ ├── i18n.ts
43
+ └── theme.ts
44
+ ```
45
+
46
+ ### 2. Build the form
47
+
48
+ Open `setup/src/App.tsx`. Read and write the settings with `useSettingsEditor`, typed by the generated `Settings`:
49
+
50
+ ```tsx
51
+ import { useSettingsEditor, useSettingsEditorCallbacks } from '@seriousgaming/sdk/ui';
52
+ import { Button, Stack, TextField } from '@mui/material';
53
+ import type { Settings } from '../types';
54
+
55
+ function App() {
56
+ const { settings, updateSettings } = useSettingsEditor<Settings>();
57
+ const { requestPlay } = useSettingsEditorCallbacks();
58
+
59
+ return (
60
+ <Stack spacing={2}>
61
+ <TextField
62
+ label="Base demand"
63
+ type="number"
64
+ value={settings.engine.BaseDemand}
65
+ onChange={(e) =>
66
+ void updateSettings({
67
+ engine: { ...settings.engine, BaseDemand: Number(e.target.value) },
68
+ })
69
+ }
70
+ />
71
+ <Button variant="contained" onClick={() => requestPlay()}>
72
+ Start game
73
+ </Button>
74
+ </Stack>
75
+ );
76
+ }
77
+
78
+ export default App;
79
+ ```
80
+
81
+ `updateSettings` sends only what changed, so spreading `settings` is safe.
82
+
83
+ ### 3. Run it
84
+
85
+ ```bash
86
+ pnpm run sdk dev
87
+ ```
88
+
89
+ The dev server opens on `/setup`.
90
+
91
+ ## Verify
92
+
93
+ - [ ] The form shows each setting's resolved value
94
+ - [ ] Changing a value and pressing Start (or the host's Play) starts the session with that value
95
+ - [ ] `pnpm run sdk view` after play shows the engine acting on the new value
96
+
97
+ ## Troubleshooting
98
+
99
+ **The build fails naming a settings resource**
100
+
101
+ Every identifier in `configures` must be a settings resource the package declares under `settings/`.
102
+
103
+ **`useSettingsEditor` throws**
104
+
105
+ The hook must run inside `SettingsEditorWrapper`, which the scaffolded `src/main.tsx` provides.
106
+
107
+ **The game starts at tick 0 with no form**
108
+
109
+ The configuration step exists only when the package has a `setup/setup.sdk.json`. Check the slot directory and bundle name.
110
+
111
+ ## Related
112
+
113
+ - [Create a scenario editor](create-scenario-editor.md)
114
+ - [Use requestPlay in a setup screen](use-request-play.md)
115
+ - [Create a settings resource](create-settings.md)
@@ -0,0 +1,65 @@
1
+ # Create a tick controller
2
+
3
+ Make ticks fire automatically, on an interval or a schedule your game's logic decides.
4
+
5
+ **Keywords:** create tick controller, new tick controller, automatic ticks, timer, tick interval, auto tick, schedule ticks
6
+
7
+ ## Prerequisites
8
+
9
+ - A capability the controller decides from (here `progress`, with a `Phase` string); see [Create a capability](create-capability.md)
10
+ - `seriousgaming.sdk.json` in your working directory
11
+
12
+ ## Steps
13
+
14
+ 1. Create the tick controller. A game holds at most one, in the `tick_controller/` slot, which the starter game leaves empty:
15
+
16
+ ```bash
17
+ pnpm run sdk new tick_controller '{
18
+ "definition": { "reads": ["progress"] },
19
+ "logic": { "directory": ".", "entrypoint": "logic.ts" },
20
+ "rootDirectory": "."
21
+ }'
22
+ ```
23
+
24
+ 2. Write `GetTickController` in `tick_controller/logic.ts`. Return a cadence with `intervalTick`, which takes milliseconds and refuses anything outside 30 seconds to 7 days:
25
+
26
+ ```typescript
27
+ import { GetTickControllerFunction, intervalTick } from '@seriousgaming/sdk';
28
+
29
+ import type { Context, Settings } from './types';
30
+
31
+ export const GetTickController: GetTickControllerFunction<Context, Settings> = async ({
32
+ context,
33
+ }) => intervalTick(context.progress.Phase === 'early' ? 30_000 : 60_000);
34
+ ```
35
+
36
+ For fixed times of day, return `scheduledTick(nextDailyAt(...))`; for manual ticks only, `{ enabled: false }`. See the [cadence cookbook](../guides/tick-controller.md#cadence-cookbook).
37
+
38
+ 3. Build:
39
+
40
+ ```bash
41
+ pnpm run build
42
+ ```
43
+
44
+ ## Verify
45
+
46
+ ```bash
47
+ pnpm run sdk timer
48
+ ```
49
+
50
+ The timer reports `enabled: true` and the next tick. In the dev server, the drawer shows the countdown and ticks fire on their own. To test without waiting, see [Testing without waiting](../guides/tick-controller.md#testing-without-waiting).
51
+
52
+ ## Troubleshooting
53
+
54
+ | Problem | Solution |
55
+ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
56
+ | `new tick_controller` fails with `RESOURCE_EXISTS` | The game already has one: change it with `update tick_controller` |
57
+ | `intervalTick(60)` throws | `intervalMs` is in milliseconds, so `60` is below the floor. Write `60_000` for a minute |
58
+ | The timer never starts | Check the controller returns `enabled: true` (or an `intervalTick` result) |
59
+ | `context.progress` is a type error | Add `progress` to the controller's `reads` |
60
+ | Ticks keep firing in the waiting room | Read `waitingRoomOpen` (see [Stopping the clock](../guides/tick-controller.md#stopping-the-clock-while-the-waiting-room-is-open)) |
61
+
62
+ ## Related
63
+
64
+ - [Tick controller guide](../guides/tick-controller.md): the result, bounds, scheduling, pausing, and completion
65
+ - [End a game when a condition is met](end-game-on-condition.md)
@@ -0,0 +1,96 @@
1
+ # Create a user interface
2
+
3
+ Build the game UI: the screen each participant sees, showing the game's state and taking their decisions.
4
+
5
+ **Keywords:** create user interface, new UI, display game state, user input, participant interface, participant UI, game UI
6
+
7
+ ## Prerequisites
8
+
9
+ - Capabilities to show and write (here `pricing` and `sales`); see [Create a capability](create-capability.md)
10
+ - An engine that fills them; see [Create an engine](create-engine.md)
11
+ - `seriousgaming.sdk.json` in your working directory
12
+
13
+ ## Steps
14
+
15
+ 1. Declare what the UI reads and writes. A game holds one UI, in the `ui/` slot, and the starter game already fills it, so in a package made with `new package` update it:
16
+
17
+ ```bash
18
+ pnpm run sdk update ui --patch '[
19
+ { "op": "replace", "path": "/definition/reads", "value": ["sales"] },
20
+ { "op": "replace", "path": "/definition/writes", "value": ["pricing"] }
21
+ ]'
22
+ ```
23
+
24
+ In a package made with `new package --empty`, create the slot instead:
25
+
26
+ ```bash
27
+ pnpm run sdk new ui '{
28
+ "definition": {
29
+ "reads": ["sales"],
30
+ "writes": ["pricing"]
31
+ },
32
+ "ui": { "entrypoint": "index.html" },
33
+ "rootDirectory": "."
34
+ }'
35
+ ```
36
+
37
+ `data` carries the union of `reads` and `writes`, so `pricing` is readable too. Either command regenerates `ui/types.ts`.
38
+
39
+ 2. Write the component in `ui/src/App.tsx`. Read with `useUserInterfaceData`, and write only the fields that changed:
40
+
41
+ ```tsx
42
+ import { useUserInterfaceData } from '@seriousgaming/sdk/ui';
43
+
44
+ import type { Context, Settings } from '../types';
45
+
46
+ export default function App() {
47
+ const { data, setData } = useUserInterfaceData<Context, Settings>();
48
+
49
+ const raisePrice = () =>
50
+ setData({ pricing: { CurrentPrice: data.pricing.CurrentPrice + 10 } });
51
+
52
+ return (
53
+ <main>
54
+ <h1>Game dashboard</h1>
55
+ <p>Current price: {data.pricing.CurrentPrice}</p>
56
+ <button onClick={raisePrice}>Raise price by 10</button>
57
+ <p>Daily revenue: {data.sales.Revenue}</p>
58
+ </main>
59
+ );
60
+ }
61
+ ```
62
+
63
+ Never spread `data` (or a whole capability) back into `setData`: a write naming a capability outside `writes` is refused, and re-sending untouched fields overwrites a team-mate's concurrent write. See [Writing to context](../guides/ui.md#writing-to-context).
64
+
65
+ 3. If you replaced the starter game's UI, delete the starter's other components and their tests under `ui/src/`: they read capabilities this UI no longer declares, so they stop type-checking.
66
+
67
+ 4. Build:
68
+
69
+ ```bash
70
+ pnpm run build
71
+ ```
72
+
73
+ ## Verify
74
+
75
+ ```bash
76
+ pnpm run sdk dev
77
+ ```
78
+
79
+ Open a team's participant view. It shows the current price and revenue, and **Raise price by 10** changes the price in every view of that team.
80
+
81
+ ## Troubleshooting
82
+
83
+ | Problem | Solution |
84
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
85
+ | `new ui` fails with `RESOURCE_EXISTS` | The slot is filled: use `update ui` (step 1), or `pnpm run sdk delete ui --yes` and then `new ui` |
86
+ | `data.pricing` is a type error | `pricing` is in neither `reads` nor `writes`; add it and let the command regenerate `types.ts` |
87
+ | The write is refused as `denied` | The write names a capability outside `writes`. Send only the capability you declared |
88
+ | A team-mate's change disappears when another saves | A write re-sends fields from a stale snapshot; send only the changed field |
89
+ | Translations do not appear | See the [internationalisation guide](../guides/internationalization.md) |
90
+
91
+ ## Related
92
+
93
+ - [User interfaces guide](../guides/ui.md): reading and writing state, ticks, participants, timers and theming
94
+ - [Design state for concurrent writes](design-state-for-concurrent-writes.md)
95
+ - [Use requestTick in a UI](use-request-tick.md)
96
+ - [Test a user interface](test-user-interface.md)
@@ -0,0 +1,67 @@
1
+ # Create a validator
2
+
3
+ Reject a participant's write that breaks the game's rules, before the runtime applies it.
4
+
5
+ **Keywords:** create validator, new validator, mutation validation, gate update, reject decision, reject submission, RequestTick
6
+
7
+ ## Prerequisites
8
+
9
+ - A capability participants write (here `decisions`, a map of boolean choices); see [Create a capability](create-capability.md)
10
+ - `seriousgaming.sdk.json` in your working directory
11
+
12
+ ## Steps
13
+
14
+ 1. Create the validator, declaring what it reads:
15
+
16
+ ```bash
17
+ pnpm run sdk new validator '{
18
+ "definition": {
19
+ "identifier": "decisionlimit",
20
+ "reads": ["decisions"]
21
+ },
22
+ "logic": { "directory": "logic", "entrypoint": "logic.ts" },
23
+ "rootDirectory": "."
24
+ }'
25
+ ```
26
+
27
+ This writes `validators/decisionlimit/` with `validator.sdk.json`, `logic/logic.ts`, and a generated `types.ts`. The directory is the whole of the wiring.
28
+
29
+ 2. Write `UpdateData` in `validators/decisionlimit/logic/logic.ts`. Judge `next`, the context as it would be after the write, and either allow it or reject it with a message:
30
+
31
+ ```typescript
32
+ import type { ValidatorFunction } from '../types';
33
+
34
+ export const UpdateData: ValidatorFunction = async ({ next }) => {
35
+ const selected = Object.values(next.decisions).filter(Boolean).length;
36
+ return selected > 3 ? { ok: false, message: 'errors.max-three-decisions' } : { ok: true };
37
+ };
38
+ ```
39
+
40
+ A validator never changes state. To read who wrote it, narrow on `actor.role` (see [The actor](../guides/validators.md#the-actor)).
41
+
42
+ 3. Optional: gate a participant's request for the next tick by exporting `RequestTick` as well (see [Gating tick requests](../guides/validators.md#gating-tick-requests)).
43
+
44
+ 4. Build:
45
+
46
+ ```bash
47
+ pnpm run build
48
+ ```
49
+
50
+ ## Verify
51
+
52
+ From a participant's screen, submit a fourth choice: the write is refused, and the UI receives the rejection through `useUserInterfaceValidationRejections` (and as a `validation-rejected` outcome where it awaits the write). In a test, `session.setData(0, ...)` answers `{ ok: false, outcome: { kind: 'validation-rejected' } }` (see [Injecting decisions](../guides/engine-testing.md#injecting-decisions)).
53
+
54
+ ## Troubleshooting
55
+
56
+ | Problem | Solution |
57
+ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | The validator never runs | Check `validators/<identifier>/validator.sdk.json` exists |
59
+ | It allows what it should reject | Judge `next`, the proposed state, not `previous` |
60
+ | A CLI patch passes despite the rule | Expected: the CLI, the engine, and endpoints are trusted and bypass validators (see [What bypasses validation](../guides/validators.md#what-bypasses-validation)) |
61
+ | `next.decisions` is a type error | Add `decisions` to the validator's `reads` |
62
+
63
+ ## Related
64
+
65
+ - [Validators guide](../guides/validators.md): the actor, ordering, settings, and what bypasses validation
66
+ - [Design state for concurrent writes](design-state-for-concurrent-writes.md): arbitrating contested slots
67
+ - [Create a hook](create-hook.md)
@@ -0,0 +1,109 @@
1
+ # Create a waiting room UI
2
+
3
+ Replace the host's stock holding screen with your own, so participants see your branding and your words while the facilitator has the waiting room open.
4
+
5
+ **Keywords:** waiting room, holding screen, lobby, waiting to begin, session paused, park a session, break, waitingRoom
6
+
7
+ ## Prerequisites
8
+
9
+ - A game; see [Shape your game](create-game.md)
10
+ - Optionally, capabilities the room should show (here `briefing`)
11
+ - `seriousgaming.sdk.json` in your working directory
12
+
13
+ ## Steps
14
+
15
+ 1. Declare what the room reads. A game holds at most one waiting room, in the `waiting_room/` slot, and the starter game already fills it, so in a package made with `new package` update it:
16
+
17
+ ```bash
18
+ pnpm run sdk update waiting_room --patch '[
19
+ { "op": "replace", "path": "/definition/reads", "value": ["briefing"] }
20
+ ]'
21
+ ```
22
+
23
+ In a package without one, create it:
24
+
25
+ ```bash
26
+ pnpm run sdk new waiting_room '{
27
+ "definition": { "reads": ["briefing"] },
28
+ "rootDirectory": ".",
29
+ "ui": { "entrypoint": "index.html" }
30
+ }'
31
+ ```
32
+
33
+ 2. Write the screen in `waiting_room/src/App.tsx`. It takes the same `useUserInterface*` hooks as the game UI. Branch on `completedTicks`, so a session parked mid-play does not say 'waiting to begin':
34
+
35
+ ```tsx
36
+ import {
37
+ useUserInterfaceData,
38
+ useUserInterfaceOwnTeam,
39
+ useUserInterfaceParticipants,
40
+ } from '@seriousgaming/sdk/ui';
41
+
42
+ import type { Context, Settings } from '../types';
43
+
44
+ export default function App() {
45
+ const { completedTicks } = useUserInterfaceData<Context, Settings>();
46
+ const ownTeam = useUserInterfaceOwnTeam();
47
+ const participants = useUserInterfaceParticipants();
48
+
49
+ return (
50
+ <main>
51
+ <h1>{completedTicks === 0 ? 'Waiting to begin' : 'Session paused'}</h1>
52
+ <p>You have joined {ownTeam?.name}. The facilitator will continue shortly.</p>
53
+ <ul>
54
+ {participants.map((p) => (
55
+ <li key={p.id}>
56
+ {p.name} {p.connected ? '(here)' : ''}
57
+ </li>
58
+ ))}
59
+ </ul>
60
+ </main>
61
+ );
62
+ }
63
+ ```
64
+
65
+ 3. Optional: open the room when a session starts, so nobody lands in the game before the facilitator is ready (the starter game already does):
66
+
67
+ ```bash
68
+ pnpm run sdk update package --patch '[
69
+ { "op": "add", "path": "/waitingRoom", "value": { "openOnStart": true } }
70
+ ]'
71
+ ```
72
+
73
+ 4. Optional: stop automatic ticks while the room is open. Opening it does not pause them; read `waitingRoomOpen` in your tick controller:
74
+
75
+ ```typescript
76
+ export const GetTickController: GetTickControllerFunction<Context, Settings> = async ({
77
+ waitingRoomOpen,
78
+ }) => ({ enabled: !waitingRoomOpen, intervalMs: 60_000 });
79
+ ```
80
+
81
+ 5. Build:
82
+
83
+ ```bash
84
+ pnpm run build
85
+ ```
86
+
87
+ ## Verify
88
+
89
+ ```bash
90
+ pnpm run sdk dev
91
+ ```
92
+
93
+ Open http://localhost:3000, press the backtick key for the dev drawer, switch on **Waiting room**, and open a team's game UI. The room replaces the game UI at once, and switching it off brings the game back with no reload. Run a tick first to see the 'Session paused' wording.
94
+
95
+ ## Troubleshooting
96
+
97
+ | Problem | Solution |
98
+ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
99
+ | `new waiting_room` fails with `RESOURCE_EXISTS` | The slot is filled: use `update waiting_room` (step 1), or `pnpm run sdk delete waiting_room --yes` first |
100
+ | The host's built-in panel shows instead | The package has no `waiting_room/` slot |
101
+ | Ticks keep firing while the room is open | Expected: read `waitingRoomOpen` in your tick controller (step 4) |
102
+ | `data` is empty | Add the capability to the bundle's `reads` |
103
+ | The roster is empty | Participants join per team; open a team's game UI rather than the console |
104
+
105
+ ## Related
106
+
107
+ - [Waiting room guide](../guides/waiting-room.md): held starts, join codes, roster, and layout
108
+ - [Create a tick controller](create-tick-controller.md)
109
+ - [Test a user interface](test-user-interface.md)