@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,55 @@
1
+ # Remember a viewer's choice
2
+
3
+ **Keywords:** useViewerStorage, localStorage, remember tab, collapsed panel, persist across reload, per-browser preference, session key, sessionKey, storage namespace, team-1 collision
4
+
5
+ A viewer's own choices, such as the tab they had open, a panel they collapsed, or a sort order, should survive a reload. Keying `localStorage` by team id looks like it works and does not: every session's first team is `team-1`, so one session's choice turns up in the next. `useViewerStorage` keys each value by the session, the surface and the viewer for you.
6
+
7
+ ## Prerequisites
8
+
9
+ - A user interface of any kind (see [Create a user interface](create-user-interface.md))
10
+
11
+ ## Steps
12
+
13
+ 1. Replace the `useState` that holds the choice with `useViewerStorage`, giving it a key and the same initial value:
14
+
15
+ ```tsx
16
+ import { useViewerStorage } from '@seriousgaming/sdk/ui';
17
+
18
+ const [tab, setTab] = useViewerStorage('tab', 0);
19
+ ```
20
+
21
+ It returns `[value, setValue]` exactly as `useState` does, updater functions included. Values are stored as JSON, so keep them to plain data.
22
+
23
+ 2. Use the value as before:
24
+
25
+ ```tsx
26
+ <Tabs value={tab} onChange={(_, value: number) => setTab(value)}>
27
+ ```
28
+
29
+ 3. Keep the key short and specific to the surface: it only has to be unique among the values one surface stores. The hook adds the session key, the surface and the viewer itself.
30
+
31
+ ## What it is keyed by
32
+
33
+ - **The run.** The host sends every surface an opaque `sessionKey`, the same for every surface of a session and different for every other session. A walkthrough has its own, so a demonstration never touches the real session's values
34
+ - **The surface.** A game UI and its waiting room share values, since both are the player's; the session UI, the settings editors and the presentation each have their own
35
+ - **The viewer.** The participant, or the viewer's role where there is none (an observer, the facilitator), so two participants sharing a device keep their own choices
36
+
37
+ It is per browser and never shared with anybody. Anything the game or other players need belongs in context, not here.
38
+
39
+ ## Verify
40
+
41
+ - Choose a tab, reload the page: the same tab opens
42
+ - Open a second participant on the same team: they start from the initial value
43
+ - In a test, `renderUserInterface(<App />, { sessionKey: 'one' })` and a second render with `sessionKey: 'two'` keep their values apart
44
+
45
+ ## Troubleshooting
46
+
47
+ - **The value does not survive a reload.** The browser is blocking storage (a private window, or a locked-down kiosk). The hook falls back to memory for the life of the page rather than throwing, which is the most it can do
48
+ - **`useViewerStorage must be used inside a surface wrapper`.** The component renders outside the wrapper, or a test renders it without one of the `render*` helpers from `@seriousgaming/sdk/ui/testing`
49
+ - **Another tab shows a stale value.** Tabs follow each other through the browser's `storage` event, which fires only for changes made in a different tab of the same origin
50
+
51
+ ## Related
52
+
53
+ - [Adapt a UI for observers](adapt-ui-for-observers.md)
54
+ - [Adapt a game for walkthroughs](adapt-a-game-for-walkthroughs.md)
55
+ - [Test a user interface](test-user-interface.md)
@@ -0,0 +1,73 @@
1
+ # Review a past day in a UI
2
+
3
+ Let participants scrub back through their team's committed ticks, or chart a value across every tick, with `usePastState` and `getPastState`.
4
+
5
+ **Keywords:** getPastState, usePastState, PastStateQuery, PastStateEntry, tick snapshot, review past day, history, scrubber, timeline, replay, previous tick, completedTicks, cross-team history, TickSnapshotError, results table, history chart, round_history growing
6
+
7
+ ## Prerequisites
8
+
9
+ - A user interface (see [Create a user interface](create-user-interface.md)) or a session UI
10
+ - The capabilities you review declared in the surface's `reads` or `writes`
11
+
12
+ ## Steps
13
+
14
+ 1. Bound the scrubber by `completedTicks`. Ticks `0` to `completedTicks - 1` are reviewable; the live board is `data`. Clamp at render, since an undo can lower `completedTicks` under a stored choice:
15
+
16
+ ```tsx
17
+ const { completedTicks } = useUserInterfaceData<Context, Settings>();
18
+ const [selectedDay, setSelectedDay] = useState(0);
19
+ const day = Math.max(0, Math.min(selectedDay, completedTicks - 1));
20
+ ```
21
+
22
+ 2. Read the board for that tick with `usePastState`. It reads again when the query changes, keeps the last board on screen while the next loads, and drops answers a newer read has overtaken:
23
+
24
+ ```tsx
25
+ import { usePastState } from '@seriousgaming/sdk/ui';
26
+
27
+ const { entries, error, loading } = usePastState<Context>(
28
+ completedTicks > 0 ? { fromTick: day } : null
29
+ );
30
+ const past = entries[0]; // undefined when that tick is not reviewable
31
+ ```
32
+
33
+ Render `past.context` with the same components as live `data`: it is composed the same way.
34
+
35
+ 3. To chart a value over every tick, read a range in one call and name only the capability you plot:
36
+
37
+ ```tsx
38
+ const { entries: history } = usePastState<Context>(
39
+ completedTicks > 0
40
+ ? { fromTick: 0, toTick: completedTicks, capabilityIds: ['profit'] }
41
+ : null
42
+ );
43
+ ```
44
+
45
+ This is what lets you drop a `round_history` capability that grows every tick.
46
+
47
+ 4. In a shared-state game, review every team at that tick with `teams: 'all'`, typed by the generated `CrossTeamContext`. Other teams' rows arrive only for capabilities the surface lists in `crossTeamReads` (see [Observe other teams' state](observe-other-teams-state.md)). A session UI calls the same `getPastState` from `useSessionUserInterfaceCallbacks()`, and its default is the whole session.
48
+
49
+ 5. Optional: for a slider dragged quickly, debounce the query, and cache entries by tick. Evict cached ticks at or above `completedTicks` when it drops, so an undo never shows a stale board.
50
+
51
+ ## Verify
52
+
53
+ - The slider runs from `0` to `completedTicks - 1` and shows each tick's board
54
+ - A decision your engine clears each tick is still visible on that tick's board
55
+ - After an undo, the undone tick is no longer reachable
56
+ - A range over `{ fromTick: 0, toTick: completedTicks }` returns every completed tick and not the live one
57
+
58
+ ## Troubleshooting
59
+
60
+ | Problem | Solution |
61
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
62
+ | Every query comes back empty | You asked for `completedTicks` or later, or no tick has run yet; only ticks below `completedTicks` are reviewable |
63
+ | What participants entered in tick 3 is not at tick 3 | A snapshot is the board the next tick was submitted with: decisions entered during tick 3 are at `{ fromTick: 2 }` |
64
+ | An entry is missing where someone did write | The write landed late and counts for the next tick (`setData` resolves with `late`) |
65
+ | A range returns fewer ticks than asked | A host may page a wide range; read on from the last tick received |
66
+ | `teams: 'all'` returns only your own team | The surface lists nothing in `crossTeamReads` |
67
+ | Reviewable history vanished | A reset deletes it; warn facilitators before they reset a live session |
68
+
69
+ ## Related
70
+
71
+ - [Reviewing past ticks](../guides/ui.md#reviewing-past-ticks): what a snapshot holds, the query, and its limits
72
+ - [Observe other teams' state](observe-other-teams-state.md)
73
+ - [Use requestTick in a UI](use-request-tick.md)
@@ -0,0 +1,120 @@
1
+ # Share assets across scenarios
2
+
3
+ Reference one file from several scenarios without copying it, using a package-level `shared:` reference.
4
+
5
+ **Keywords:** shared assets, shared-assets, share media across scenarios, shared: reference, deduplicate scenario assets, storyline assets, avoid asset duplication
6
+
7
+ ## What you'll build
8
+
9
+ Two scenarios that play the same storyline and reference the same media file from a single source under `shared-assets/`, so editing that one file changes both.
10
+
11
+ ## Prerequisites
12
+
13
+ - A package with at least two scenarios (see [Create a Scenario](create-scenario.md))
14
+ - The media the scenarios share (images, audio, video, documents)
15
+
16
+ ## When to use this
17
+
18
+ Use a `shared:` reference when two or more scenarios point at the same file and you do not want to maintain duplicate copies. Asset duplication is the part that drifts: a `shared:` reference keeps a single source of truth.
19
+
20
+ Keep using a scenario's own `asset:<path>` when a file genuinely belongs to one scenario, or when a scenario needs its own version of a file that another scenario shares. There is no shadowing between the two schemes: `shared:x` and `asset:x` are independent.
21
+
22
+ ## Steps
23
+
24
+ ### 1. Create the shared-assets directory
25
+
26
+ Place the shared files under a top-level `shared-assets/` directory at the package root (next to `scenarios/`, `engine/`, and `seriousgaming.sdk.json`). Group with folders however you like; there is no schema or declaration:
27
+
28
+ ```
29
+ my-package/
30
+ ├── shared-assets/
31
+ │ └── storyline/
32
+ │ ├── avatars/ceo.png
33
+ │ └── video/intro.mp4
34
+ ├── scenarios/
35
+ │ ├── agilisco/scenario.sdk.json
36
+ │ └── growth/scenario.sdk.json
37
+ └── seriousgaming.sdk.json
38
+ ```
39
+
40
+ ### 2. Declare the setting the scenarios override
41
+
42
+ A scenario can only override a setting a settings resource declares: an undeclared key fails the build. Create a `storyline` settings resource holding `Avatar`, with a default for games run without a scenario:
43
+
44
+ ```bash
45
+ pnpm run sdk new settings '{
46
+ "definition": {
47
+ "identifier": "storyline",
48
+ "settingsSchema": {
49
+ "type": "object",
50
+ "properties": {
51
+ "Avatar": { "type": "string", "default": "shared:storyline/avatars/ceo.png" }
52
+ },
53
+ "additionalProperties": false
54
+ }
55
+ }
56
+ }'
57
+ ```
58
+
59
+ Then add `storyline` to the `settings` array of each resource that shows the avatar, such as the game UI's `ui/ui.sdk.json`. A resource receives only the settings it lists.
60
+
61
+ ### 3. Reference the shared file from each scenario
62
+
63
+ Use a `shared:<path>` reference anywhere an `asset:` reference is valid; `<path>` is relative to `shared-assets/`. Point both scenarios at the same file. A JSON Patch `add` needs the parent to exist, so if a scenario has no `settings.storyline` block yet, add the block rather than the key (`"path": "/definition/settings/storyline", "value": { "Avatar": "..." }`):
64
+
65
+ ```bash
66
+ pnpm run sdk update scenario agilisco --patch '[
67
+ { "op": "add",
68
+ "path": "/definition/settings/storyline/Avatar",
69
+ "value": "shared:storyline/avatars/ceo.png" }
70
+ ]'
71
+
72
+ pnpm run sdk update scenario growth --patch '[
73
+ { "op": "add",
74
+ "path": "/definition/settings/storyline/Avatar",
75
+ "value": "shared:storyline/avatars/ceo.png" }
76
+ ]'
77
+ ```
78
+
79
+ The `growth` preset can still override a setting the two scenarios differ on: duplicated settings values are fine. Only the media is shared.
80
+
81
+ ### 4. Use the reference in a UI or engine
82
+
83
+ Nothing special is needed: `useAssetUrl`, `useAsset`, `getAssetUrl`, `readAsset`, and `extractText` all handle `shared:` like any other reference.
84
+
85
+ ```tsx
86
+ import { useAssetUrl, useUserInterfaceData } from '@seriousgaming/sdk/ui';
87
+
88
+ import type { Context, Settings } from '../types';
89
+
90
+ function Avatar() {
91
+ const { settings } = useUserInterfaceData<Context, Settings>();
92
+ return <img src={useAssetUrl(settings.storyline.Avatar)} alt="CEO" />;
93
+ }
94
+ ```
95
+
96
+ ## Verify
97
+
98
+ ```bash
99
+ # Build copies shared-assets/ into the bundle root
100
+ pnpm run sdk build
101
+ ls dist/shared-assets/storyline/avatars # ceo.png is present
102
+
103
+ # Bundle includes shared-assets/ exactly once
104
+ pnpm run sdk package bundle
105
+ ```
106
+
107
+ Start the dev server and confirm the file is served at `/api/shared/assets/storyline/avatars/ceo.png`, then switch between the two scenarios and confirm both render the same image. A `shared:` reference resolves even before any scenario is selected.
108
+
109
+ ## Troubleshooting
110
+
111
+ - **404 from the shared asset URL** - the path inside `shared:<path>` is relative to `shared-assets/`, not to a scenario's `assets/`. Check the file exists under `shared-assets/<path>` and that you ran `build` so it was copied into `dist/`
112
+ - **The wrong file resolves** - `shared:` and `asset:` never shadow each other. If you meant the scenario's own copy, use `asset:<path>`; if you meant the shared copy, use `shared:<path>`
113
+ - **`getAssetUrl` or `useAssetUrl` throws** - the reference must start with `asset:`, `shared:`, or `blob:`. A bare path is not a valid reference
114
+
115
+ ## Related
116
+
117
+ - [Asset references](../guides/file-handling.md#asset-references)
118
+ - [Create a Scenario](create-scenario.md)
119
+ - [Create a settings resource](create-settings.md)
120
+ - [Scenarios guide](../guides/scenarios.md)
@@ -0,0 +1,130 @@
1
+ # Share logic between an engine and a UI
2
+
3
+ **Keywords:** share code, shared logic, duplicate formula, projection, preview, keep in sync, pure helper, import from engine and UI, single source of truth, drift
4
+
5
+ Put a calculation in one file and use it from both your engine logic and your user interface, so a live preview cannot drift from the result the engine actually computes.
6
+
7
+ ## When you need this
8
+
9
+ Whenever a UI shows a participant what _would_ happen. An engine settles the real outcome at tick time, but the participant wants to see the effect of a decision before submitting it, so the same formula has to run in two places:
10
+
11
+ - 'At this price you would win 38% of the market'
12
+ - 'This trade would leave you 12 short'
13
+ - 'Your score after this move would be 340'
14
+
15
+ Writing the formula twice works right up until someone changes one copy. Then the preview lies, and participants stop trusting the interface.
16
+
17
+ ## Prerequisites
18
+
19
+ - An engine with logic (see [Create an engine](create-engine.md))
20
+ - A user interface (see [Create a user interface](create-user-interface.md))
21
+
22
+ ## Steps
23
+
24
+ 1. Put the calculation in a plain TypeScript file anywhere in your package. A top-level `shared/` directory keeps it obvious that it is not owned by one resource:
25
+
26
+ ```
27
+ my-package/
28
+ ├── shared/
29
+ │ └── market.ts ← the shared rules
30
+ ├── engine/
31
+ │ └── logic.ts
32
+ └── ui/
33
+ └── src/App.tsx
34
+ ```
35
+
36
+ 2. Export pure functions. No callbacks, no context mutation, no I/O: just inputs to outputs:
37
+
38
+ ```typescript
39
+ // shared/market.ts
40
+
41
+ /** Share of the market each team wins, given every team's price. */
42
+ export function marketShares(prices: number[]): number[] {
43
+ const inverses = prices.map((price) => 1 / price);
44
+ const total = inverses.reduce((sum, value) => sum + value, 0);
45
+ return inverses.map((inverse) => inverse / total);
46
+ }
47
+
48
+ /** The share one team would win at `candidatePrice`, rivals unchanged. */
49
+ export function projectShare(
50
+ candidatePrice: number,
51
+ ownIndex: number,
52
+ currentPrices: number[]
53
+ ): number {
54
+ const hypothetical = currentPrices.map((price, index) =>
55
+ index === ownIndex ? candidatePrice : price
56
+ );
57
+ return marketShares(hypothetical)[ownIndex];
58
+ }
59
+ ```
60
+
61
+ 3. Import it from your engine's logic. The engine stays the authority: it computes the value that is written to state.
62
+
63
+ ```typescript
64
+ // engine/logic.ts
65
+ import { marketShares } from '../shared/market';
66
+
67
+ export const RunAggregate: RunAggregateFunction<TeamContext, SharedContext, Settings> = async ({
68
+ teams,
69
+ }) => {
70
+ const prices = teams.map((t) => t.context.price.Price);
71
+ const shares = marketShares(prices);
72
+
73
+ return {
74
+ shared: { capabilities: {} },
75
+ teams: teams.map((t, i) => ({
76
+ capabilities: {
77
+ ...t.context,
78
+ market: { ...t.context.market, Share: shares[i] },
79
+ },
80
+ })),
81
+ };
82
+ };
83
+ ```
84
+
85
+ 4. Import the same file from your UI, and use it only to project. Never to decide:
86
+
87
+ ```tsx
88
+ // ui/src/App.tsx
89
+ import { projectShare } from '../../shared/market';
90
+ import { useUserInterfaceCrossTeamData, useUserInterfaceOwnTeam } from '@seriousgaming/sdk/ui';
91
+
92
+ import type { CrossTeamContext } from '../types';
93
+
94
+ const ownTeam = useUserInterfaceOwnTeam();
95
+ const teams = useUserInterfaceCrossTeamData<CrossTeamContext>();
96
+
97
+ const ownIndex = teams.findIndex((team) => team.teamId === ownTeam?.teamId);
98
+ const projected =
99
+ ownIndex === -1
100
+ ? null
101
+ : projectShare(
102
+ price,
103
+ ownIndex,
104
+ teams.map((team) => team.context.price.Price)
105
+ );
106
+ ```
107
+
108
+ 5. Keep the shared file within two rules. Both sides bundle what they import (esbuild for engine logic, Vite for UIs), so the file's code is inlined into each bundle and nothing resolves it at runtime. That is also why it inherits the constraints of both:
109
+
110
+ - **Keep it pure.** Inputs to outputs. A shared function that reaches for `callbacks`, mutates context, or performs I/O cannot be used by both sides, and stops being safe to call from a render
111
+ - **Stay inside the ECMAScript intrinsics.** The file is compiled into a logic bundle, so it inherits the [bare-V8 restriction](../guides/engine.md#the-language-environment-your-logic-runs-in): no `structuredClone`, `fetch`, timers, `TextEncoder`, and so on. The scaffold's lint rule covers `engine/`, `tick_controller/` and each plural resource's `logic/` directory, so a top-level `shared/` file is **on you**. To have the lint rule cover it, put the file inside `engine/` instead and import it from the UI by relative path
112
+
113
+ ## Verify
114
+
115
+ - `pnpm run sdk build` completes, and `dist/engine/logic.js` contains the shared function inlined
116
+ - `pnpm run sdk tick` produces the value the engine computes through the shared function
117
+ - The UI's preview and the post-tick value agree for the same inputs
118
+
119
+ ## Troubleshooting
120
+
121
+ - **`ReferenceError` at tick time naming something from the shared file**: The shared file used a host global. See step 5; the dev server and `tick` both reproduce this, so fix it there
122
+ - **The UI cannot resolve the import**: Check the relative depth. From `ui/src/App.tsx` to `shared/` is two levels up
123
+ - **The preview disagrees with the result**: The engine is passing different inputs, not running different code. Log both sides' arguments; the usual cause is the UI projecting from live cross-team values while the engine settles from the post-validator state
124
+ - **Type errors only in the engine**: The shared file is being type-checked against both the UI's and the engine's tsconfig. Keep its signatures free of DOM and Node types
125
+
126
+ ## Related
127
+
128
+ - [Negotiate across teams](negotiate-across-teams.md): the pattern this most often serves
129
+ - [Observe other teams' state](observe-other-teams-state.md)
130
+ - [Engines guide: the language environment your logic runs in](../guides/engine.md#the-language-environment-your-logic-runs-in)
@@ -0,0 +1,109 @@
1
+ # Show all teams' participants in a UI
2
+
3
+ Let a game UI read the participant roster of every team in a shared-state session, so it can render a directory of who is on each competing team.
4
+
5
+ **Keywords:** getAllTeamsParticipants, crossTeamParticipants, all teams participants, participant directory, who is on each team, cross-team roster, negotiation, useUserInterfaceParticipants, Participant
6
+
7
+ ## What you'll build
8
+
9
+ A user interface that lists every team and its participants, grouped by team, for a negotiation or trading game where teams deal with each other live.
10
+
11
+ ## Prerequisites
12
+
13
+ - A shared-state game (`teams.mode` is `shared-state`); the declaration below fails the build in any other mode
14
+ - An existing user interface (see [Create a user interface](create-user-interface.md)) included in that game
15
+
16
+ ## Steps
17
+
18
+ ### 1. Declare cross-team participant access
19
+
20
+ Add `crossTeamParticipants: true` to the UI's bundle. This authorises the roster read and makes it visible at build time. Without it, `getAllTeamsParticipants()` resolves to an empty array.
21
+
22
+ ```json
23
+ {
24
+ "definition": {
25
+ "crossTeamParticipants": true
26
+ }
27
+ }
28
+ ```
29
+
30
+ It is only meaningful in shared-state games: in any other mode the build fails.
31
+
32
+ ### 2. Read every team's roster
33
+
34
+ Call `getAllTeamsParticipants` from `useUserInterfaceCallbacks()`. It returns one flat `Participant[]` (`{ id, name, index, connected, teamId }`), one entry per participant across all teams.
35
+
36
+ ```typescript
37
+ import { useUserInterfaceCallbacks, type Participant } from '@seriousgaming/sdk/ui';
38
+
39
+ const { getAllTeamsParticipants } = useUserInterfaceCallbacks();
40
+
41
+ const [roster, setRoster] = useState<Participant[]>([]);
42
+ useEffect(() => {
43
+ let cancelled = false;
44
+ void getAllTeamsParticipants().then((all) => {
45
+ if (!cancelled) setRoster(all);
46
+ });
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [getAllTeamsParticipants]);
51
+ ```
52
+
53
+ ### 3. Group by team and render
54
+
55
+ The roster is flat; group it by `teamId` and join against `useUserInterfaceTeams()` for each team's display name and index.
56
+
57
+ ```typescript
58
+ import { useUserInterfaceTeams } from '@seriousgaming/sdk/ui';
59
+
60
+ const teams = useUserInterfaceTeams();
61
+ const byTeam = useMemo(() => {
62
+ const grouped = new Map<string, Participant[]>();
63
+ for (const p of roster) {
64
+ grouped.set(p.teamId, [...(grouped.get(p.teamId) ?? []), p]);
65
+ }
66
+ return grouped;
67
+ }, [roster]);
68
+
69
+ // teams.map((team) => byTeam.get(team.teamId) ?? []), then render team.name then its members
70
+ ```
71
+
72
+ ### 4. Refresh when the roster may have changed
73
+
74
+ `getAllTeamsParticipants` is a point-in-time snapshot, not a live subscription: it does not update as participants join or leave. Re-call it when you want fresh data, for example on each committed tick by depending on `completedTicks`.
75
+
76
+ ```typescript
77
+ const { completedTicks } = useUserInterfaceData<Context, Settings>();
78
+ useEffect(() => {
79
+ void getAllTeamsParticipants().then(setRoster);
80
+ }, [completedTicks, getAllTeamsParticipants]);
81
+ ```
82
+
83
+ Your own team's roster stays live through `useUserInterfaceParticipants()`: use that hook for the caller's own team and `getAllTeamsParticipants` for the wider directory.
84
+
85
+ ## Verify
86
+
87
+ - [ ] The directory lists every team and its participants, grouped by team
88
+ - [ ] A participant that joins another team appears after you refresh (a new tick, or your own refresh trigger)
89
+ - [ ] Removing `crossTeamParticipants` from the bundle makes the directory empty
90
+ - [ ] Declaring `crossTeamParticipants` in a non-shared-state game fails `sdk build`
91
+
92
+ ## Troubleshooting
93
+
94
+ **`getAllTeamsParticipants()` returns an empty array**
95
+
96
+ The UI has not declared `crossTeamParticipants: true` in its bundle, or the game is not shared-state. The declaration is the gate; an undeclared UI always receives an empty array.
97
+
98
+ **The directory does not update when someone joins**
99
+
100
+ That is expected: the read is point-in-time. Trigger a refresh (step 4). Only the caller's own team updates live, via `useUserInterfaceParticipants()`.
101
+
102
+ **`useUserInterfaceCallbacks` throws**
103
+
104
+ The hook must be called inside a component rendered within a `UserInterfaceWrapper`. The SDK template wires this automatically.
105
+
106
+ ## Related
107
+
108
+ - [Observe other teams' state](observe-other-teams-state.md)
109
+ - [Building user interfaces](../guides/ui.md)
@@ -0,0 +1,169 @@
1
+ # Start a game from an empty package
2
+
3
+ Build a game from nothing: a capability for its state, an engine that changes it each tick, and a game UI to show it.
4
+
5
+ **Keywords:** empty package, from scratch, new game, blank game, new package --empty, first capability, first engine
6
+
7
+ ## What you'll build
8
+
9
+ A bakery that bakes a number of loaves each day and sells up to the day's demand, with a running count of loaves sold.
10
+
11
+ ## Prerequisites
12
+
13
+ - Node.js 24 or later and pnpm 12
14
+ - An empty directory
15
+ - [Getting started](../getting-started.md) worked through once. The starter game shows the shape a real game needs, which an empty package does not
16
+
17
+ ## Steps
18
+
19
+ ### 1. Scaffold an empty package
20
+
21
+ ```bash
22
+ mkdir bakery
23
+ cd bakery
24
+ pnpm dlx @seriousgaming/sdk-tools new package --empty
25
+ ```
26
+
27
+ This writes the package files (`seriousgaming.sdk.json`, `package.json`, the TypeScript, lint, and test configuration), the same `CLAUDE.md` and `.claude/` guidance a starter game gets, and installs dependencies. It adds no game resources, so there is nothing to run yet.
28
+
29
+ ### 2. Create a capability
30
+
31
+ A capability is a named slice of the game's state. This one holds the loaves:
32
+
33
+ ```bash
34
+ pnpm run sdk new capability '{
35
+ "definition": {
36
+ "identifier": "bakery",
37
+ "variablesSchema": {
38
+ "type": "object",
39
+ "properties": {
40
+ "Loaves": { "type": "number", "default": 20, "minimum": 0 },
41
+ "Sold": { "type": "number", "default": 0 }
42
+ },
43
+ "required": ["Loaves", "Sold"],
44
+ "additionalProperties": false
45
+ }
46
+ }
47
+ }'
48
+ ```
49
+
50
+ The identifier is the directory name, so this writes `capabilities/bakery/capability.sdk.json`.
51
+
52
+ ### 3. Create the engine
53
+
54
+ A game holds one engine, at `engine/`. It declares what it reads and writes, and the settings it takes. Create the settings first; every property declares a default:
55
+
56
+ ```bash
57
+ pnpm run sdk new settings '{
58
+ "definition": {
59
+ "identifier": "demand",
60
+ "settingsSchema": {
61
+ "type": "object",
62
+ "properties": {
63
+ "DailyDemand": { "type": "number", "default": 15, "minimum": 0 }
64
+ },
65
+ "additionalProperties": false
66
+ }
67
+ }
68
+ }'
69
+ ```
70
+
71
+ This writes `settings/demand/settings.sdk.json`. Then create the engine, listing `demand` in its `settings`:
72
+
73
+ ```bash
74
+ pnpm run sdk new engine '{
75
+ "definition": {
76
+ "reads": ["bakery"],
77
+ "writes": ["bakery"],
78
+ "settings": ["demand"]
79
+ },
80
+ "logic": { "directory": ".", "entrypoint": "logic.ts" },
81
+ "rootDirectory": "."
82
+ }'
83
+ ```
84
+
85
+ This writes `engine/engine.sdk.json`, `engine/logic.ts`, `engine/logic.test.ts`, and a generated `engine/types.ts`.
86
+
87
+ ### 4. Write the logic
88
+
89
+ Replace the body of `engine/logic.ts`:
90
+
91
+ ```typescript
92
+ import { InitializationFunction, RunFunction } from '@seriousgaming/sdk';
93
+
94
+ import type { Callbacks, Context, Settings } from './types';
95
+
96
+ export const Init: InitializationFunction<Context, Settings, Callbacks> = async ({ context }) => {
97
+ return { capabilities: { ...context, bakery: { ...context.bakery, Sold: 0 } } };
98
+ };
99
+
100
+ export const Run: RunFunction<Context, Settings, Callbacks> = async ({ context, settings }) => {
101
+ const sold = Math.min(context.bakery.Loaves, settings.demand.DailyDemand);
102
+ return {
103
+ capabilities: {
104
+ ...context,
105
+ bakery: { ...context.bakery, Sold: context.bakery.Sold + sold },
106
+ },
107
+ };
108
+ };
109
+ ```
110
+
111
+ Always return a new object: spread the context and the capability you change, never assign into them.
112
+
113
+ ### 5. Add a game UI
114
+
115
+ ```bash
116
+ pnpm run sdk new ui '{
117
+ "definition": {
118
+ "reads": ["bakery"],
119
+ "writes": ["bakery"]
120
+ },
121
+ "ui": { "entrypoint": "index.html" },
122
+ "rootDirectory": "."
123
+ }'
124
+ ```
125
+
126
+ This writes `ui/`, the game UI each participant sees. Edit `ui/src/App.tsx` to show `data.bakery` and to write `Loaves` with `setData`; [Building user interfaces](../guides/ui.md) covers both.
127
+
128
+ ### 6. Set how long the game runs
129
+
130
+ ```bash
131
+ pnpm run sdk update package --patch '[{"op":"add","path":"/ticks","value":5}]'
132
+ ```
133
+
134
+ `seriousgaming.sdk.json` now ends the game after five ticks.
135
+
136
+ ## Verify
137
+
138
+ Play a tick with 10 loaves baked:
139
+
140
+ ```bash
141
+ pnpm run sdk tick --patch '[{"op":"replace","path":"/bakery/Loaves","value":10}]'
142
+ ```
143
+
144
+ The output shows `"completedTicks": 1` and `"Sold": 10`. Then check the package builds and its tests pass:
145
+
146
+ ```bash
147
+ pnpm run sdk build --validate-only
148
+ pnpm typecheck
149
+ pnpm test:run
150
+ ```
151
+
152
+ ## Troubleshooting
153
+
154
+ | Problem | Solution |
155
+ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
156
+ | `new engine` refuses because one already exists | The package was not made with `--empty`. Change the existing engine with `update engine` instead |
157
+ | `context.bakery` is a type error | The engine does not declare `bakery` in `reads` or `writes`. Add it with `update engine`, which regenerates `types.ts` |
158
+ | `logic.test.ts` fails to typecheck | Keep the `Callbacks` type argument on `Init` and `Run`: the generated test passes `Callbacks`, not the default callback set |
159
+ | The tick changes nothing | Check `Run` returns the new capability object rather than the context it received |
160
+
161
+ ## Related
162
+
163
+ - [Getting started](../getting-started.md)
164
+ - [Create a capability](./create-capability.md)
165
+ - [Create an engine](./create-engine.md)
166
+ - [Create a settings resource](./create-settings.md)
167
+ - [Create a user interface](./create-user-interface.md)
168
+ - [Shape your game](./create-game.md)
169
+ - [Design state for concurrent writes](./design-state-for-concurrent-writes.md)