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

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 +38 -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 +27404 -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 +16920 -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 +218 -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 +241 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +156 -0
  71. package/dist/docs/cli-reference.md +2489 -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 +577 -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 +339 -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 +123 -3
@@ -0,0 +1,82 @@
1
+ # Take a file in an endpoint
2
+
3
+ **Keywords:** file upload, upload a file to an endpoint, image upload, PDF upload, blob input, data URI, base64, attach file, AI agent upload, MCP file, API upload, script upload
4
+
5
+ Let an endpoint accept a file, such as a team photo or a research PDF, from a game UI, a script calling the API, or an AI agent.
6
+
7
+ ## Prerequisites
8
+
9
+ - An endpoint that already works (see [Create an endpoint](create-endpoint.md))
10
+ - Somewhere for the file to go: usually a capability holding its `blob:` reference, which a screen shows with `getAssetUrl` or logic reads with `readAsset` or `extractText`
11
+
12
+ ## Steps
13
+
14
+ 1. Mark the request property as a file with `blob`, saying which types it takes and how large:
15
+
16
+ ```json
17
+ {
18
+ "definition": {
19
+ "requestSchema": {
20
+ "type": "object",
21
+ "properties": {
22
+ "document": {
23
+ "type": "string",
24
+ "description": "The research, as a PDF.",
25
+ "blob": { "accept": ["application/pdf"], "maxBytes": 5242880 }
26
+ }
27
+ },
28
+ "required": ["document"],
29
+ "additionalProperties": false
30
+ },
31
+ "writes": ["upload"]
32
+ }
33
+ }
34
+ ```
35
+
36
+ `accept` takes media types and `type/*` wildcards (`"image/*"`), and allows any type when left out. The property must be `"type": "string"`, or the build fails.
37
+
38
+ 2. Regenerate the types, so the field is typed as a reference:
39
+
40
+ ```bash
41
+ pnpm run sdk generate
42
+ ```
43
+
44
+ `Request['document']` is now `` `blob:${string}` ``.
45
+
46
+ 3. Use the reference in the handler:
47
+
48
+ ```typescript
49
+ export const Handle: Handler = async ({ request, context }) => ({
50
+ response: { replaced: context.upload.DocumentBlobId !== '' },
51
+ capabilities: { upload: { ...context.upload, DocumentBlobId: request.document } },
52
+ });
53
+ ```
54
+
55
+ The runtime has already checked the file against the declaration, including its real type from its bytes rather than the one the caller claimed, and stored it. There is nothing to decode or check here. To read the bytes, call `callbacks.readAsset(request.document)`.
56
+
57
+ 4. If the endpoint is for callers outside the game, say how to send the file in its description (see [Make an endpoint callable from outside the game](make-an-endpoint-external.md)):
58
+
59
+ > Send a PDF of at most 5 MB: inline as a data: URI, or upload it first and send the blob: reference.
60
+
61
+ A caller sends either a `blob:` reference or a `data:` URI, never a URL (see [Files in and out](../guides/endpoints.md#files-in-and-out)).
62
+
63
+ ## Verify
64
+
65
+ - `pnpm run sdk endpoint call attachresearch --external --team 1 --attach document=./research.pdf` succeeds, and `pnpm run sdk view` shows the reference on the team's board
66
+ - `pnpm run sdk blob upload ./research.pdf`, then the call with `--request '{"document": "blob:..."}'`, succeeds the same way
67
+ - The same call with a PNG is refused before your handler runs
68
+
69
+ ## Troubleshooting
70
+
71
+ - _'must be a blob: reference or a data: URI'_: The caller sent something else, commonly a URL or a bare id without `blob:`
72
+ - _'claims image/png, but its bytes are application/pdf'_: The `data:` URI's type does not match the file. Fix the type the caller sends
73
+ - _'is image/gif, which is not one of ...'_: Widen `accept`, or send a type it lists
74
+ - _'names a blob this session does not hold'_: The reference is wrong, or the file was uploaded to a different session
75
+ - _'carries more than ... bytes a data: URI may carry inline'_: Upload the file first and send the reference
76
+ - _'declares `blob` but is not `"type": "string"`'_: A file field is a string; change its type
77
+
78
+ ## Related
79
+
80
+ - [Endpoints (concept)](../guides/endpoints.md)
81
+ - [File handling](../guides/file-handling.md)
82
+ - [Make an endpoint callable from outside the game](make-an-endpoint-external.md)
@@ -0,0 +1,51 @@
1
+ # Test a multi-role game in the dev server
2
+
3
+ Drive a game that needs several participants per team (for example a buyer and a seller who each act before a tick advances) from a single dev-server session, using the per-team participant roster.
4
+
5
+ **Keywords:** multiple participants, roles, multi-role, participant roster, add participant, rename participant, open participant, buyer seller, connected, /play participant
6
+
7
+ ## What you'll build
8
+
9
+ A running session where one team has several named participants, each opened in its own browser tab and acting independently, so a role-based interaction can be played through end to end.
10
+
11
+ ## Prerequisites
12
+
13
+ - A game whose UI branches on participant identity via `useUserInterfaceParticipant()` (see [Create a user interface](create-user-interface.md))
14
+ - The dev server running (`pnpm run sdk dev`)
15
+
16
+ ## Steps
17
+
18
+ ### 1. Open the console and expand a team
19
+
20
+ A fresh team starts with one participant, at `/team/<n>/player/1`. Participants are positions in the URL, 1-based, so there is no id to look up and nothing to resolve.
21
+
22
+ ### 2. Add and name the participants you need
23
+
24
+ Use **Add participant** on the team's row in the dev drawer to create each role, or `pnpm run sdk participant add Buyer --team 1` from the CLI, which names it too. To rename one later, use `pnpm run sdk participant rename <participantId> <name>`; `pnpm run sdk view` lists the ids. Creating a participant fires the `participantJoin` hook, exactly as it would in production, so any join-driven setup runs.
25
+
26
+ If the game declares `teams.perTeam.minParticipants` / `maxParticipants`, the roster shows an advisory caption (for example '2–5 recommended for this scenario'). It is guidance only and never blocks adding or removing.
27
+
28
+ ### 3. Open each participant in its own tab
29
+
30
+ Open `/team/<n>/player/<p>` for each role, in its own tab, and arrange them side by side. Each tab holds its own live connection, so every participant shows as connected at the same time, which is what a multi-role game gates on.
31
+
32
+ ### 4. Drive the interaction
33
+
34
+ Act as each role in its tab. Because each tab carries its own participant identity, every state mutation is attributed to the right actor, so per-participant validators see the correct actor and the game advances once the roles have acted.
35
+
36
+ ## Verify
37
+
38
+ - The team's row in the dev drawer counts every participant you added
39
+ - Opening two participants in two tabs shows both as connected in `pnpm run sdk view` (each participant carries `connected`)
40
+ - Renaming a participant updates its name in every open view
41
+
42
+ ## Troubleshooting
43
+
44
+ - **'No such participant' when opening a link.** The URL asks for a seat the team does not have: opening `/team/1/player/3` before adding a third participant, or following a stale link. The screen says how many the team holds and offers **Add participant**. A second tab is not a second participant, so add them in step 2 first. The dev server does not invent one, and does not serve an unidentified surface: a write it cannot attribute to a participant would skip the game's validators, so it is refused instead
45
+ - **A participant shows as disconnected.** Its tab is closed. Connection state is live only; it is never persisted, so every participant starts disconnected after a dev-server restart even though its name is restored
46
+ - **A new participant did not trigger role setup.** Role assignment usually lives in a `participantJoin` hook. Confirm the game declares the hook (see [Create a hook](create-hook.md))
47
+
48
+ ## Related
49
+
50
+ - [Accessing participant info](../guides/ui.md#accessing-participant-info)
51
+ - [Test UIs end to end with Playwright](e2e-testing.md): drive several participants from a test
@@ -0,0 +1,95 @@
1
+ # Test a user interface
2
+
3
+ Write and run component tests for a user interface with Vitest, React Testing Library, and the SDK's render helpers.
4
+
5
+ **Keywords:** test UI, unit test, React testing, App.test.tsx, vitest, testing library, mock context, setData, controlled input, renderUserInterface, renderSessionUserInterface, renderSettingsEditor, mock participant, participant roster, sessionKey
6
+
7
+ ## Prerequisites
8
+
9
+ - A user interface, with the scaffolded `vitest.config.ts`, `test-setup.ts`, and `src/App.test.tsx`
10
+
11
+ ## Steps
12
+
13
+ 1. Open the test beside the component: `ui/src/App.test.tsx` for the game UI (`session/src/`, `waiting_room/src/`, `setup/src/` and the others for theirs).
14
+
15
+ 2. Render through the helper for the surface, giving it only what the test cares about. Every other context value has a complete default, so never build the providers by hand:
16
+
17
+ ```tsx
18
+ import { fireEvent, screen } from '@testing-library/react';
19
+ import { renderUserInterface } from '@seriousgaming/sdk/ui/testing';
20
+ import { describe, expect, it, vi } from 'vitest';
21
+
22
+ import { defaultContext, defaultSettings } from '../defaults';
23
+ import './i18n';
24
+ import App from './App';
25
+
26
+ describe('Dashboard', () => {
27
+ it('shows the current stock', () => {
28
+ renderUserInterface(<App />, {
29
+ data: { ...defaultContext, inventory: { Stock: 100 } },
30
+ settings: defaultSettings,
31
+ });
32
+ expect(screen.getByLabelText(/stock/i)).toHaveValue(100);
33
+ });
34
+ });
35
+ ```
36
+
37
+ Use `renderSessionUserInterface` for a session UI and `renderSettingsEditor` for setup and the scenario editor (see [Using render helpers](../guides/testing.md#using-render-helpers)).
38
+
39
+ 3. Assert on writes with a mock `setData`. Drive a controlled input with `fireEvent.change`, since a mocked `setData` never updates the displayed value between keystrokes:
40
+
41
+ ```tsx
42
+ it('writes the new price', () => {
43
+ const setData = vi.fn();
44
+ renderUserInterface(<App />, { data: defaultContext, settings: defaultSettings, setData });
45
+
46
+ fireEvent.change(screen.getByLabelText(/price/i), { target: { value: '50' } });
47
+
48
+ expect(setData).toHaveBeenCalledWith({ pricing: { Price: 50 } });
49
+ });
50
+ ```
51
+
52
+ 4. Pass any callback you assert on. Callbacks you leave out default to no-ops:
53
+
54
+ ```tsx
55
+ const requestTick = vi.fn();
56
+ renderUserInterface(<App />, {
57
+ data: defaultContext,
58
+ settings: defaultSettings,
59
+ callbacks: { requestTick },
60
+ });
61
+ ```
62
+
63
+ 5. For a participant-aware UI, pass `participant` and `participants`, built with `createMockParticipant` and `createMockParticipants`. The roster defaults to `[]`, the real first-render state:
64
+
65
+ ```tsx
66
+ import { createMockParticipants } from '@seriousgaming/sdk/ui/testing';
67
+
68
+ renderUserInterface(<App />, { data: defaultContext, participants: createMockParticipants(3) });
69
+ ```
70
+
71
+ 6. Run the tests:
72
+
73
+ ```bash
74
+ pnpm run test
75
+ ```
76
+
77
+ ## Verify
78
+
79
+ Every test passes, and `pnpm typecheck` accepts the mock data: `defaultContext` and `defaultSettings` come from the generated `defaults.ts`, so a missing capability is a type error.
80
+
81
+ ## Troubleshooting
82
+
83
+ | Problem | Solution |
84
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
85
+ | 'Invalid hook call' | Two copies of React; see [Fix duplicate package errors in a UI](fix-duplicate-packages.md) |
86
+ | 'Found multiple elements with the text: X' | `test-setup.ts` must run `cleanup` after each test |
87
+ | `setData` is never called | `userEvent.type` on a controlled input; use `fireEvent.change` |
88
+ | A hook throws 'must be used inside' | The component was rendered without a helper. Render through `renderUserInterface` or its siblings |
89
+ | A write the game accepts fails the test | The mock does not merge as the host does; use `mergingSetData` (see [Design state for concurrent writes](design-state-for-concurrent-writes.md)) |
90
+
91
+ ## Related
92
+
93
+ - [Testing guide](../guides/testing.md): every render helper and option
94
+ - [Test a whole game in-process](test-with-engine.md)
95
+ - [Test UIs end to end with Playwright](e2e-testing.md)
@@ -0,0 +1,115 @@
1
+ # Test a whole game in-process
2
+
3
+ Test your whole game (engine, hooks, validators, endpoints, and completion) in one Node.js process, with no dev server, browser, or subprocess.
4
+
5
+ **Keywords:** engine test, createTestSession, test session, in-process test, vitest engine, test game logic, programmatic client, setData, tick
6
+
7
+ ## Prerequisites
8
+
9
+ - A game package with its dependencies installed. One made with `sdk new package` already has the engine test setup: skip to step 4
10
+
11
+ ## Steps
12
+
13
+ 1. Add the test session's package, if it is not already a dependency:
14
+
15
+ ```bash
16
+ pnpm add -D @seriousgaming/sdk-tools
17
+ ```
18
+
19
+ 2. Create `vitest.engine.config.ts` in the package root:
20
+
21
+ ```typescript
22
+ import { defineConfig } from 'vitest/config';
23
+
24
+ export default defineConfig({
25
+ test: {
26
+ environment: 'node',
27
+ testTimeout: 60_000,
28
+ include: ['testing/engine/**/*.engine.test.ts'],
29
+ },
30
+ });
31
+ ```
32
+
33
+ In `vitest.config.ts`, add `exclude: ['testing/**', '**/node_modules/**']` to `test`, so the jsdom and engine configs do not pick up each other's files.
34
+
35
+ 3. Add the script to `package.json`:
36
+
37
+ ```json
38
+ { "scripts": { "test:engine": "vitest run --config vitest.engine.config.ts" } }
39
+ ```
40
+
41
+ 4. Write `testing/engine/game.engine.test.ts`. Type the session with the engine's generated `Context` and `SharedContext`, so its helpers need no casts:
42
+
43
+ ```typescript
44
+ import { fileURLToPath } from 'url';
45
+
46
+ import { createTestSession, type TestSession } from '@seriousgaming/sdk-tools/client';
47
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
48
+
49
+ import type { Context, SharedContext } from '../../engine/types';
50
+
51
+ const PACKAGE_PATH = fileURLToPath(new URL('../..', import.meta.url));
52
+
53
+ describe('mygame', () => {
54
+ let session: TestSession<Context, SharedContext>;
55
+
56
+ beforeEach(async () => {
57
+ session = await createTestSession<Context, SharedContext>({
58
+ packagePath: PACKAGE_PATH,
59
+ });
60
+ });
61
+
62
+ afterEach(async () => {
63
+ await session.close();
64
+ });
65
+
66
+ it('calculates revenue from workers and price', async () => {
67
+ await session.setData(0, { workers: { WorkerCount: 20 }, pricing: { Price: 10 } });
68
+
69
+ expect((await session.tick()).ok).toBe(true);
70
+
71
+ const { context } = session.team(0);
72
+ expect(context.production.Production).toBe(1000);
73
+ expect(context.revenue.Revenue).toBe(10_000);
74
+ });
75
+ });
76
+ ```
77
+
78
+ `setData(0, ...)` writes as team 0's first participant through the game UI, so the write is held to the UI's `writes` and your validators judge it. Assert on a computed value, not only on the value you wrote.
79
+
80
+ 5. Optional: choose what the model answers. A test session answers `generateText`, `generateSpeech`, and `extractText` from offline stubs; override one with `callbacks`:
81
+
82
+ ```typescript
83
+ session = await createTestSession<Context, SharedContext>({
84
+ packagePath: PACKAGE_PATH,
85
+ callbacks: {
86
+ generateText: async () => ({
87
+ content: [{ type: 'text' as const, text: 'mock response' }],
88
+ stopReason: 'endTurn' as const,
89
+ }),
90
+ },
91
+ });
92
+ ```
93
+
94
+ ## Verify
95
+
96
+ ```bash
97
+ pnpm test:engine
98
+ ```
99
+
100
+ Every test passes, each in well under a second.
101
+
102
+ ## Troubleshooting
103
+
104
+ | Problem | Solution |
105
+ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
106
+ | `TextEncoder` invariant in the default test run | The jsdom config picked up the engine test. Add `testing/**` to `exclude` in `vitest.config.ts` (step 2) |
107
+ | A context field is a type error | The capability is not in the `Context` you passed; intersect the hook's `Context` if a hook keeps it |
108
+ | `setData` answers `validation-rejected` | Your validator refused it. Pass `{ as: 'system' }` to arrange state the test then plays on |
109
+ | Tests are slow or time out | A test passes `services: 'platform'`, or a callback override calls the network |
110
+
111
+ ## Related
112
+
113
+ - [Engine testing guide](../guides/engine-testing.md): every `createTestSession` option, completion, endpoints, timers, and scenarios
114
+ - [Testing guide](../guides/testing.md): unit tests with `logicArgs`, and component tests
115
+ - [Test UIs end to end with Playwright](e2e-testing.md)
@@ -0,0 +1,148 @@
1
+ # Upload and display files
2
+
3
+ Let a participant attach a file (a PDF, CSV, or image) in the game UI, show it back, and optionally summarise it in the engine.
4
+
5
+ **Keywords:** upload file, file upload, attach document, blob, asset reference, uploadBlob, getAssetUrl, useAssetUrl, extractText, embed PDF
6
+
7
+ ## Prerequisites
8
+
9
+ - A game with an engine and a game UI
10
+ - For step 5, platform access for the LLM (see [LLM integration](../guides/llm-integration.md#configuration))
11
+
12
+ ## Steps
13
+
14
+ 1. Create a capability to hold the file's reference:
15
+
16
+ ```bash
17
+ pnpm run sdk new capability '{
18
+ "definition": {
19
+ "identifier": "upload",
20
+ "variablesSchema": {
21
+ "type": "object",
22
+ "properties": {
23
+ "DocumentRef": { "type": "string", "default": "" }
24
+ },
25
+ "required": ["DocumentRef"],
26
+ "additionalProperties": false
27
+ }
28
+ }
29
+ }'
30
+ ```
31
+
32
+ 2. Let the game UI write it (`writes` makes it readable too):
33
+
34
+ ```bash
35
+ pnpm run sdk update ui --patch '[
36
+ { "op": "add", "path": "/definition/writes/-", "value": "upload" }
37
+ ]'
38
+ ```
39
+
40
+ 3. Upload from the UI and store the reference. Read the empty string as 'nothing uploaded':
41
+
42
+ ```tsx
43
+ import {
44
+ useAssetUrl,
45
+ useUserInterfaceCallbacks,
46
+ useUserInterfaceData,
47
+ } from '@seriousgaming/sdk/ui';
48
+
49
+ import type { Context, Settings } from '../types';
50
+
51
+ export function ResearchPanel() {
52
+ const { uploadBlob } = useUserInterfaceCallbacks();
53
+ const { data, setData } = useUserInterfaceData<Context, Settings>();
54
+ const reference = data.upload.DocumentRef;
55
+ const url = useAssetUrl(reference); // undefined while the reference is ''
56
+
57
+ const onFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
58
+ const file = event.target.files?.[0];
59
+ if (!file) return;
60
+ await setData({ upload: { DocumentRef: await uploadBlob(file) } });
61
+ };
62
+
63
+ return url ? (
64
+ <embed src={url} type="application/pdf" style={{ width: '100%', height: 500 }} />
65
+ ) : (
66
+ <input type="file" accept=".pdf" onChange={onFile} />
67
+ );
68
+ }
69
+ ```
70
+
71
+ 4. Optional: create the capability the engine writes its summary to, and let the engine read the upload, write the summary, and call the two services. Calls to a service the engine does not list in `uses` throw:
72
+
73
+ ```bash
74
+ pnpm run sdk new capability '{
75
+ "definition": {
76
+ "identifier": "summary",
77
+ "variablesSchema": {
78
+ "type": "object",
79
+ "properties": { "Summary": { "type": "string", "default": "" } },
80
+ "required": ["Summary"],
81
+ "additionalProperties": false
82
+ }
83
+ }
84
+ }'
85
+ pnpm run sdk update engine --patch '[
86
+ { "op": "add", "path": "/definition/reads/-", "value": "upload" },
87
+ { "op": "add", "path": "/definition/writes/-", "value": "summary" },
88
+ { "op": "add", "path": "/definition/uses", "value": ["extractText", "generateText"] }
89
+ ]'
90
+ ```
91
+
92
+ If the engine already declares `uses`, append to it instead (`"path": "/definition/uses/-"`).
93
+
94
+ 5. Optional: summarise the file in the engine's `Run`:
95
+
96
+ ```typescript
97
+ import { getText, type RunFunction } from '@seriousgaming/sdk';
98
+
99
+ import type { Callbacks, Context, Settings } from './types';
100
+
101
+ export const Run: RunFunction<Context, Settings, Callbacks> = async ({
102
+ context,
103
+ callbacks,
104
+ }) => {
105
+ const reference = context.upload.DocumentRef;
106
+ if (!reference) return { capabilities: context };
107
+
108
+ const { content } = await callbacks.extractText(reference);
109
+ const result = await callbacks.generateText({
110
+ prompt: `Summarise this market research:\n\n${content}`,
111
+ tier: 'standard',
112
+ });
113
+ return {
114
+ capabilities: { ...context, summary: { ...context.summary, Summary: getText(result) } },
115
+ };
116
+ };
117
+ ```
118
+
119
+ Typed with the generated `Callbacks`, both services are present rather than optional. For raw bytes rather than text, use `readAsset` (see [Reading an asset in an engine](../guides/file-handling.md#reading-an-asset-in-an-engine)).
120
+
121
+ ## Verify
122
+
123
+ From the CLI:
124
+
125
+ ```bash
126
+ REFERENCE=$(pnpm run --silent sdk blob upload ./test.pdf --json | jq -r '.data.reference')
127
+ pnpm run sdk patch "[{\"op\":\"replace\",\"path\":\"/upload/DocumentRef\",\"value\":\"$REFERENCE\"}]"
128
+ pnpm run sdk tick
129
+ pnpm run sdk view
130
+ ```
131
+
132
+ `summary.Summary` holds the model's summary. In the dev server, uploading a PDF from a team's screen replaces the input with the embedded document.
133
+
134
+ ## Troubleshooting
135
+
136
+ | Problem | Solution |
137
+ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
138
+ | `data.upload` is a type error | `upload` is not in the UI's `reads` or `writes`; step 2 regenerates `types.ts` |
139
+ | `UndeclaredApiUseError` at tick time | The engine calls a service missing from its `uses` (step 4) |
140
+ | The engine never sees the reference | Add `upload` to the engine's `reads` |
141
+ | The PDF does not render | Match the `<embed>` type to the file; see [Supported file types](../guides/file-handling.md#supported-file-types) |
142
+ | `LLMClientError` from `generateText` | Platform access is not configured; see [Environment variables](../environment-variables.md) |
143
+
144
+ ## Related
145
+
146
+ - [File handling guide](../guides/file-handling.md): references, uploading, displaying, and reading files
147
+ - [Declare the runtime callbacks a resource uses](declare-api-uses.md)
148
+ - [Take a file in an endpoint](take-a-file-in-an-endpoint.md)
@@ -0,0 +1,65 @@
1
+ # Use requestPlay in a setup screen
2
+
3
+ Leave the configuration step and start the game from within your setup screen, so operators can move to play from your own control as well as from the host's Play button.
4
+
5
+ **Keywords:** requestPlay, request play, start game, setup play, custom play button, setup submit, leave configuration step
6
+
7
+ ## What you'll build
8
+
9
+ A setup screen with a custom 'Start' button that moves the session from its configuration step to play when clicked. The host's own Play button stays: a runtime always renders it, and no game can hide it.
10
+
11
+ ## Prerequisites
12
+
13
+ - An existing setup screen (see [Create a setup screen](create-setup.md))
14
+
15
+ ## Steps
16
+
17
+ ### 1. Call `requestPlay` from your setup screen
18
+
19
+ Use the `useSettingsEditorCallbacks` hook and call `requestPlay` when the operator is ready to move on:
20
+
21
+ ```typescript
22
+ import { useSettingsEditorCallbacks } from '@seriousgaming/sdk/ui';
23
+
24
+ function SetupPanel() {
25
+ const { requestPlay } = useSettingsEditorCallbacks();
26
+
27
+ return (
28
+ <button onClick={() => requestPlay()}>
29
+ Start game
30
+ </button>
31
+ );
32
+ }
33
+ ```
34
+
35
+ ### 2. Start the dev server and verify
36
+
37
+ ```bash
38
+ pnpm run sdk dev
39
+ ```
40
+
41
+ Click your custom button. The session should leave the configuration step and start play.
42
+
43
+ ## Verify
44
+
45
+ - [ ] Clicking the custom button moves the session from the configuration step to play
46
+ - [ ] Any setting changes made before clicking are preserved
47
+
48
+ ## Troubleshooting
49
+
50
+ **Nothing happens when clicking the button in the scenario editor**
51
+
52
+ That is expected. `requestPlay` means something only in `setup`; the host ignores it from `scenario_editor`, which may have no session to start. The callback is present in both because one wrapper, `SettingsEditorWrapper`, serves both slots.
53
+
54
+ **Two Play controls are visible**
55
+
56
+ That is expected. The host always renders its own, so your control is an addition rather than a replacement: a form with its own finish button at the bottom, say. A game cannot hide the host's, because a bug in your own control would otherwise strand a facilitator on the configuration step with no way forward.
57
+
58
+ **`useSettingsEditorCallbacks` throws an error**
59
+
60
+ The hook must be called inside a component rendered within `SettingsEditorWrapper`. The scaffolded `src/main.tsx` provides it, so check your component is a descendant of it.
61
+
62
+ ## Related
63
+
64
+ - [Use requestTick in a UI](./use-request-tick.md)
65
+ - [Building a setup screen](../guides/setup.md)
@@ -0,0 +1,75 @@
1
+ # Use requestTick in a UI
2
+
3
+ Trigger the next game tick from within your user interface, so participants submit their decisions via your own button. This is how games advance: there is no built-in next tick button, so every game provides its own tick control.
4
+
5
+ **Keywords:** requestTick, request tick, submit, next tick, end turn, custom submit button, tick from UI
6
+
7
+ ## What you'll build
8
+
9
+ A user interface with a submit button that advances the game to the next tick when clicked.
10
+
11
+ ## Prerequisites
12
+
13
+ - An existing user interface (see [Create a user interface](create-user-interface.md))
14
+ - An existing game that includes the UI (see [Shape your game](create-game.md))
15
+
16
+ ## Steps
17
+
18
+ ### 1. Call `requestTick` from your UI component
19
+
20
+ Use the `useUserInterfaceCallbacks` hook and call `requestTick` when the participant submits:
21
+
22
+ ```typescript
23
+ import { useUserInterfaceCallbacks } from '@seriousgaming/sdk/ui';
24
+
25
+ function DecisionPanel() {
26
+ const callbacks = useUserInterfaceCallbacks();
27
+
28
+ const handleSubmit = async () => {
29
+ await callbacks.requestTick();
30
+ };
31
+
32
+ return (
33
+ <button onClick={handleSubmit}>
34
+ Submit Decision
35
+ </button>
36
+ );
37
+ }
38
+ ```
39
+
40
+ `requestTick` is a required member of `UserInterfaceCallbacks` and returns a `Promise<void>`. It resolves once the tick has finished and context is updated, and rejects with a `MutationError` when the tick cannot run (for example, the tick limit has been reached).
41
+
42
+ The same callback is available to session UIs, so a session UI provides its own tick controls the same way (the scaffolded starter package wires all of them).
43
+
44
+ ### 2. Start the dev server and verify
45
+
46
+ ```bash
47
+ pnpm run sdk dev
48
+ ```
49
+
50
+ Click your custom button. The game should advance to the next tick and your UI should receive the updated context.
51
+
52
+ During development you can also advance ticks without a game control: the dev drawer (press the backtick key) has a developer-only **Tick** button.
53
+
54
+ ## Verify
55
+
56
+ - [ ] Clicking the custom button advances the tick counter
57
+ - [ ] Context updates are reflected in your UI after each tick
58
+
59
+ ## Troubleshooting
60
+
61
+ **Nothing happens when clicking the button**
62
+
63
+ Await the returned promise and catch the rejection: a tick the host refuses rejects with a `MutationError` rather than failing silently.
64
+
65
+ **`useUserInterfaceCallbacks` throws an error**
66
+
67
+ The hook must be called inside a component rendered within `UserInterfaceWrapper`. This is provided automatically by the SDK template, so check your component is a descendant of `UserInterfaceWrapper`.
68
+
69
+ **UI shows a brief flicker after calling `setData` then `requestTick`**
70
+
71
+ When you call `setData({ ... }); callbacks.requestTick();`, the `setData` change is visible immediately but the tick is asynchronous. Between the two, UIs show the raw `setData` values without any engine-computed results. This resolves once the tick completes (typically a few milliseconds). See [Combining setData with requestTick](../guides/ui.md#combining-setdata-with-requesttick) for strategies to handle the intermediate state.
72
+
73
+ ## Related
74
+
75
+ - [User interfaces guide: requesting a tick](../guides/ui.md#requesting-a-tick)