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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +36 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27368 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16884 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +209 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +575 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +335 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +122 -3
@@ -0,0 +1,405 @@
1
+ # File handling (assets and blobs)
2
+
3
+ The SDK reads every file, whether it ships in the package or arrives at runtime, through one reference scheme. This guide explains the scheme, then covers uploading files at runtime, displaying them in UIs, and reading their bytes in logic.
4
+
5
+ Common uses include uploading a market research PDF in a decisions UI, displaying it in a results UI, and summarising it with an LLM inside an engine.
6
+
7
+ ## Asset references
8
+
9
+ Every file a game can read is named by a self-describing string:
10
+
11
+ ```
12
+ asset:<path> // a file inside the active scenario's assets/ directory
13
+ shared:<path> // a file inside the package-level shared-assets/ directory
14
+ blob:<id> // a file uploaded at runtime, in the blob store
15
+ ```
16
+
17
+ Any string that begins with `asset:`, `shared:`, or `blob:` is an asset reference, and no other string is. So settings and context can mix scenario files, package files, and uploads freely: the prefix tells the resolver which store to read, and the code that uses a reference never needs to know which kind it holds.
18
+
19
+ - **`asset:`** is scenario-scoped. It resolves against the active scenario's `assets/` directory, and resolves to nothing when no scenario is active
20
+ - **`shared:`** is package-level. It resolves against `shared-assets/` at the package root, whatever scenario is active, or none (see [Sharing assets across scenarios](./scenarios.md#sharing-assets-across-scenarios))
21
+ - **`blob:`** names an upload. `uploadBlob` in a UI and `writeBlob` in logic both return one, so store it directly in context: there is no separate id to track
22
+
23
+ `asset:` and `shared:` are disjoint: there is no shadowing or precedence between them.
24
+
25
+ ### The callbacks
26
+
27
+ Every callback that works on a file takes a reference:
28
+
29
+ - **`getAssetUrl(reference)`** returns a URL the browser can fetch, for `<img>`, `<embed>`, `<video>` and the like. Synchronous, and in a UI only. In a hosted runtime the URL is on the runtime's asset host, cross-origin to the surface's iframe. `useAssetUrl` is the hook form
30
+ - **`readAsset(reference)`** returns the bytes, MIME type, and display name. Asynchronous. In logic the runtime resolves the reference server-side; in a UI the SDK fetches the URL with `credentials: 'include'`. `useAsset` is the hook form that reads text, JSON, or a `Blob`
31
+ - **`extractText(reference)`** returns plain text from a PDF. It is offered to the engine, hooks, tick controllers, and endpoints that declare it in `uses`; a UI reaches it through an endpoint
32
+
33
+ In a UI, `getAssetUrl` strips the prefix and appends the rest to an absolute prefix the host supplies for each scheme. Under `sdk dev` those prefixes are on the asset origin (`--asset-port`), so `asset:shared/hero.jpg` becomes `http://localhost:<asset-port>/assets/scenario/shared/hero.jpg`; a hosted runtime points them at its own asset host. With no scenario active an `asset:` reference still produces a URL, which answers 404. A string with none of the three prefixes throws.
34
+
35
+ ### Writing references in a scenario
36
+
37
+ A scenario writes references with the prefix in place, usually as values of settings the engine and UIs read:
38
+
39
+ ```json
40
+ {
41
+ "definition": {
42
+ "settings": {
43
+ "briefing": {
44
+ "Logo": "asset:shared/logo.svg",
45
+ "ScenarioBrief": "asset:en/intro.pdf"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ Here `briefing` is a settings resource (`settings/briefing/`) declaring `Logo` and `ScenarioBrief` as strings with an empty default, which the engine and UI examples below list in their `settings`. The runtime takes these strings as they are: nothing checks at scenario load that the file exists, and a reference that does not resolve fails when something calls `getAssetUrl`, `readAsset`, or `extractText` with it. See [Scenarios](./scenarios.md#2-add-assets-optional) for where the files go.
53
+
54
+ ### How a file moves through a game
55
+
56
+ ```
57
+ User uploads file in decisions UI
58
+ ↓
59
+ uploadBlob(file) → returns 'blob:<id>'
60
+ ↓
61
+ setData({ 'upload': { DocumentRef: reference } })
62
+ ↓
63
+ Next tick: engine reads bytes via readAsset(reference)
64
+ Results UI displays via getAssetUrl(reference)
65
+ ```
66
+
67
+ ## Uploading a file in a UI
68
+
69
+ Use the `uploadBlob` callback from `useUserInterfaceCallbacks`. It returns a `blob:`-prefixed reference; store it directly:
70
+
71
+ ```typescript
72
+ import { useUserInterfaceCallbacks, useUserInterfaceData } from '@seriousgaming/sdk/ui';
73
+ import type { Context, Settings } from '../types';
74
+
75
+ function DocumentUpload() {
76
+ const callbacks = useUserInterfaceCallbacks();
77
+ const { setData } = useUserInterfaceData<Context, Settings>();
78
+ const [isUploading, setIsUploading] = useState(false);
79
+
80
+ const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
81
+ const file = e.target.files?.[0];
82
+ if (!file) return;
83
+
84
+ try {
85
+ setIsUploading(true);
86
+ const reference = await callbacks.uploadBlob(file);
87
+
88
+ setData({
89
+ upload: { DocumentRef: reference },
90
+ });
91
+ } finally {
92
+ setIsUploading(false);
93
+ e.target.value = '';
94
+ }
95
+ };
96
+
97
+ return (
98
+ <div>
99
+ <input
100
+ type="file"
101
+ accept=".pdf,.csv,.txt"
102
+ onChange={handleFileUpload}
103
+ disabled={isUploading}
104
+ />
105
+ {isUploading && <p>Uploading...</p>}
106
+ </div>
107
+ );
108
+ }
109
+ ```
110
+
111
+ **Notes:**
112
+
113
+ - You do not choose the blob's lifetime: the host assigns it from the UI type (see [Blob scopes](#blob-scopes) below)
114
+ - The maximum file size is **20 MB**
115
+ - The returned string starts with `blob:`. Persist it in context via `setData` so the engine and other UIs can resolve it
116
+
117
+ ## Displaying a file in a UI
118
+
119
+ Use `useAssetUrl` to resolve any reference to a URL suitable for `<img>`, `<embed>`, `<video>`, or any HTML element that takes a `src`. It returns `undefined` for an absent reference. Whether the reference came from an uploader (`blob:abc123`) or a scenario (`asset:shared/intro.pdf`), the component is the same:
120
+
121
+ ```typescript
122
+ import { useAssetUrl, useUserInterfaceData } from '@seriousgaming/sdk/ui';
123
+ import type { Context, Settings } from '../types';
124
+
125
+ function DocumentViewer() {
126
+ const { data, settings } = useUserInterfaceData<Context, Settings>();
127
+ const src = useAssetUrl(data.upload?.DocumentRef || settings.briefing.ScenarioBrief);
128
+
129
+ if (!src) {
130
+ return <p>No document uploaded.</p>;
131
+ }
132
+
133
+ return (
134
+ <embed
135
+ src={src}
136
+ type="application/pdf"
137
+ style={{ width: '100%', height: '500px', border: 'none' }}
138
+ />
139
+ );
140
+ }
141
+ ```
142
+
143
+ For images, including a logo bundled in a scenario:
144
+
145
+ ```tsx
146
+ const logo = useAssetUrl('asset:shared/logo.svg');
147
+ return <img src={logo} alt="Logo" />;
148
+ ```
149
+
150
+ Outside a component, the surface's `getAssetUrl` callback does the same. To render a file's contents rather than link to it, such as a localised briefing, use `useAsset(reference, { as: 'text' })`, which fetches and reads it (see [Reading an asset's contents](./ui.md#reading-an-assets-contents)).
151
+
152
+ ## Reading an asset in an engine
153
+
154
+ The engine receives `readAsset` in its callbacks. It accepts an `asset:`, `shared:`, or `blob:` reference and returns the bytes plus metadata:
155
+
156
+ ```typescript
157
+ import { decodeUtf8, getText, type RunFunction } from '@seriousgaming/sdk';
158
+ import type { Context, Settings } from './types';
159
+
160
+ export const Run: RunFunction<Context, Settings> = async ({ context, callbacks }) => {
161
+ const reference = context.upload?.DocumentRef;
162
+
163
+ if (reference) {
164
+ const { data, mimeType, name } = await callbacks.readAsset(reference);
165
+
166
+ const text = decodeUtf8(data);
167
+
168
+ const summary = await callbacks.generateText({
169
+ prompt: `Summarise this document:\n\n${text}`,
170
+ tier: 'standard',
171
+ });
172
+
173
+ return {
174
+ capabilities: {
175
+ ...context,
176
+ summary: {
177
+ ...context.summary,
178
+ Summary: getText(summary),
179
+ },
180
+ },
181
+ };
182
+ }
183
+
184
+ return { capabilities: context };
185
+ };
186
+ ```
187
+
188
+ **Notes:**
189
+
190
+ - `readAsset` needs no `uses` entry: every engine, hook, tick controller, and endpoint has it
191
+ - `data` is a `Uint8Array`. Use `decodeUtf8` for text formats; pass binary directly to downstream APIs
192
+ - The same callback resolves `asset:shared/brief.pdf` and `blob:abc123`: there is no separate API for bundled and uploaded files
193
+
194
+ ## Extracting text from a document
195
+
196
+ Use the `extractText` callback to convert a document into plain text suitable for passing to an LLM. Available in the engine, hooks, tick controllers, and endpoints that declare `"extractText"` in `uses`. UIs reach it through an endpoint. Currently supports **PDF** files.
197
+
198
+ ### In an engine
199
+
200
+ Declare `"uses": ["extractText", "generateText"]` on the engine, and pass the generated `Callbacks` as `RunFunction`'s third type argument so both are typed as present:
201
+
202
+ ```typescript
203
+ import { getText, type RunFunction } from '@seriousgaming/sdk';
204
+ import type { Callbacks, Context, Settings } from './types';
205
+
206
+ export const Run: RunFunction<Context, Settings, Callbacks> = async ({
207
+ context,
208
+ settings,
209
+ callbacks,
210
+ }) => {
211
+ // An upload if there is one, else the brief the scenario supplies
212
+ const reference = context.upload?.DocumentRef || settings.briefing.ScenarioBrief;
213
+
214
+ if (reference) {
215
+ const { content } = await callbacks.extractText(reference);
216
+
217
+ const summary = await callbacks.generateText({
218
+ prompt: `Summarise this document:\n\n${content}`,
219
+ tier: 'standard',
220
+ });
221
+
222
+ return {
223
+ capabilities: {
224
+ ...context,
225
+ summary: {
226
+ ...context.summary,
227
+ Summary: getText(summary),
228
+ },
229
+ },
230
+ };
231
+ }
232
+
233
+ return { capabilities: context };
234
+ };
235
+ ```
236
+
237
+ ### From a UI, through an endpoint
238
+
239
+ UIs cannot call `extractText` directly. Upload the document with `uploadBlob`, then pass the returned reference to an endpoint that wraps `extractText`:
240
+
241
+ ```typescript
242
+ // endpoints/extract/logic/logic.ts
243
+ import type { Handler } from '../types';
244
+
245
+ export const Handle: Handler = async ({ request, callbacks }) => {
246
+ // Declared in `uses`, so always present: no check needed.
247
+ const { content } = await callbacks.extractText(request.reference);
248
+ return { text: content };
249
+ };
250
+ ```
251
+
252
+ The endpoint bundle declares `"uses": ["extractText"]`; the UI declares the endpoint in its `endpoints` array and calls it:
253
+
254
+ ```tsx
255
+ import { useState } from 'react';
256
+ import { useUserInterfaceCallbacks } from '@seriousgaming/sdk/ui';
257
+ import type { Callbacks } from '../types';
258
+
259
+ function DocumentProcessor() {
260
+ const callbacks = useUserInterfaceCallbacks() as Callbacks;
261
+ const [extractedText, setExtractedText] = useState<string | null>(null);
262
+
263
+ const handleExtract = async (reference: string) => {
264
+ const { text } = await callbacks.endpoints['extract']({ reference });
265
+ setExtractedText(text);
266
+ };
267
+
268
+ return extractedText ? <pre>{extractedText}</pre> : <p>No text extracted.</p>;
269
+ }
270
+ ```
271
+
272
+ See [Call an endpoint from a UI](../recipes/call-endpoint-from-ui.md).
273
+
274
+ **Notes:**
275
+
276
+ - Without the generated `Callbacks` type argument, `extractText` is typed as optional, and a resource that calls it without declaring it in `uses` throws at run time
277
+ - The extracted text is plain text. For PDFs, the quality depends on the source document (text-based PDFs extract well; scanned documents may not)
278
+
279
+ ## Sending a file to an endpoint
280
+
281
+ An endpoint that takes a file declares it in its request schema with `blob`, so the runtime checks the file before the handler runs:
282
+
283
+ ```json
284
+ "reference": {
285
+ "type": "string",
286
+ "blob": { "accept": ["application/pdf"], "maxBytes": 5242880 }
287
+ }
288
+ ```
289
+
290
+ The field accepts two kinds of string, so the same endpoint serves every caller:
291
+
292
+ - **A `blob:` reference** to a file already stored. A UI passes the reference `uploadBlob` returned, as in the example above; a script or an agent uploads first (locally, `POST /api/blobs` or `pnpm run sdk blob upload`)
293
+ - **A `data:` URI** carrying the file inline, for a caller with no upload of its own: `data:application/pdf;name=brief.pdf;base64,...`. Up to 5 MB
294
+
295
+ Either way the handler receives a `blob:` reference, typed `` `blob:${string}` ``, that names a file of an accepted type, judged by its bytes, within the size. A URL, a wrong type or an oversized file is refused before the handler runs. Without `blob`, a field is a plain string the handler must check itself.
296
+
297
+ See [Take a file in an endpoint](../recipes/take-a-file-in-an-endpoint.md).
298
+
299
+ ## Blob scopes
300
+
301
+ Every blob is stored with a retention that controls its lifetime:
302
+
303
+ | Retention | Lifetime | When to use |
304
+ | ------------ | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
305
+ | `"game"` | Cleared on a session reset or a re-initialise | Participant-uploaded content for a specific game session |
306
+ | `"settings"` | Survives a session reset, cleared on a re-initialise | Documents set up before the game starts (e.g. a scenario brief uploaded from a setup screen or scenario editor) |
307
+
308
+ Retention is a deletion-lifecycle bucket only: it is not an access or visibility control. It is set at upload time, and who sets it depends on the surface:
309
+
310
+ - **UIs** never choose the retention. The host assigns it from the UI type: uploads from setup or the scenario editor are `"settings"`, every other UI's uploads are `"game"`. The `uploadBlob` callback takes no retention option
311
+ - **Engine / endpoint `writeBlob`** has no UI to derive from, so it takes an explicit `retention` option (default `"game"`)
312
+ - **CLI uploads** default to `"game"` retention but accept `--settings` to use `"settings"` retention
313
+
314
+ A deleted blob's reference answers 404, as an unknown one does. In the dev server, `sdk init`, the console's Re-initialise and Reconfigure, and selecting a scenario clear both retentions. The automatic re-initialise after a package edit clears `"game"` blobs only, so the scenario editor's uploads survive an edit. A per-team reset clears nothing, since blobs are not held per team, and neither does resetting the walkthrough.
315
+
316
+ ## Uploading a blob via CLI
317
+
318
+ You can pre-load a file into a game session from the command line:
319
+
320
+ ```bash
321
+ pnpm run sdk blob upload <file-path>
322
+ ```
323
+
324
+ **Options:**
325
+
326
+ - `--settings`: store with `'settings'` retention (survives a session reset). Default is `'game'` retention
327
+ - `--json`: print one JSON document for scripts, with the result as its `data`
328
+
329
+ **Output (default):**
330
+
331
+ ```
332
+ Uploaded blob: blob:a3f7c2d1-84b0-4e12-9f1a-0c7e8a3b1d5e
333
+ File: market-research.pdf
334
+ Type: application/pdf
335
+ Retention: game
336
+ ```
337
+
338
+ **Output (`--json`):** one JSON document (see [JSON output](../cli-reference.md#json-output)), whose `data` is:
339
+
340
+ ```json
341
+ {
342
+ "reference": "blob:a3f7c2d1-84b0-4e12-9f1a-0c7e8a3b1d5e",
343
+ "name": "market-research.pdf",
344
+ "mimeType": "application/pdf",
345
+ "retention": "game"
346
+ }
347
+ ```
348
+
349
+ After uploading, patch the reference into the game context so the engine can find it:
350
+
351
+ ```bash
352
+ REFERENCE=$(pnpm run --silent sdk blob upload market-research.pdf --json | jq -r '.data.reference')
353
+ pnpm run sdk patch \
354
+ "[{\"op\":\"replace\",\"path\":\"/upload/DocumentRef\",\"value\":\"$REFERENCE\"}]"
355
+ ```
356
+
357
+ ## Supported file types
358
+
359
+ MIME types are detected from the file extension:
360
+
361
+ | Extension | MIME type |
362
+ | --------------- | -------------------------- |
363
+ | `.pdf` | `application/pdf` |
364
+ | `.png` | `image/png` |
365
+ | `.jpg`, `.jpeg` | `image/jpeg` |
366
+ | `.gif` | `image/gif` |
367
+ | `.webp` | `image/webp` |
368
+ | `.svg` | `image/svg+xml` |
369
+ | `.mp4` | `video/mp4` |
370
+ | `.webm` | `video/webm` |
371
+ | `.mp3` | `audio/mpeg` |
372
+ | `.wav` | `audio/wav` |
373
+ | `.json` | `application/json` |
374
+ | `.csv` | `text/csv` |
375
+ | `.txt` | `text/plain` |
376
+ | Other | `application/octet-stream` |
377
+
378
+ ## Capability design
379
+
380
+ Define a capability to hold asset references:
381
+
382
+ ```bash
383
+ pnpm run sdk new capability '{
384
+ "definition": {
385
+ "identifier": "upload",
386
+ "variablesSchema": {
387
+ "type": "object",
388
+ "properties": {
389
+ "DocumentRef": { "type": "string", "default": "" }
390
+ },
391
+ "required": ["DocumentRef"],
392
+ "additionalProperties": false
393
+ }
394
+ }
395
+ }'
396
+ ```
397
+
398
+ The directory is the declaration: there is no list in `seriousgaming.sdk.json` to register it in. Then reference it from the resources that need it:
399
+
400
+ - The **game UI** (`ui/`) lists `upload` in both its `writes` and its `reads`: the decisions and results panels are both components inside it
401
+ - Any **engine** that processes the document lists `upload` in its `reads`
402
+
403
+ ## Example
404
+
405
+ Put together, the pieces above make an upload round trip. One component inside the game UI uploads a PDF with the code in [Uploading a file in a UI](#uploading-a-file-in-a-ui) and writes the returned reference to `upload.DocumentRef`; another reads `upload.DocumentRef` and embeds the file with the code in [Displaying a file in a UI](#displaying-a-file-in-a-ui). The engine reads the same reference when it needs the document's contents, as [Reading an asset in an engine](#reading-an-asset-in-an-engine) shows.
@@ -0,0 +1,176 @@
1
+ # Shaping games
2
+
3
+ A package is a game. This guide shows you how to shape the one you have.
4
+
5
+ ## What you'll learn
6
+
7
+ - Where a game is authored
8
+ - How to configure the engine and game UI it runs
9
+ - How to add a setup screen and a scenario editor
10
+ - How to set up multi-language support
11
+ - Where to read about goal-based games with dynamic end conditions
12
+
13
+ ## Where the game lives
14
+
15
+ The game is `seriousgaming.sdk.json`: the manifest is the package, and the package is the game.
16
+
17
+ ```json
18
+ {
19
+ "identifier": "com.example.mygame",
20
+ "supportedLocales": ["en"],
21
+ "locales": { "en": { "name": "My Game" } },
22
+ "maxTicks": 10
23
+ }
24
+ ```
25
+
26
+ The game's own identifier is `com.example.mygame.game`, the package identifier with `.game` appended, so it is never authored. The version is `package.json`'s. What the game wires is whatever the package declares, and what the package declares is whatever sits in the conventional directories beside this file:
27
+
28
+ ```
29
+ com.example.mygame/
30
+ ├── seriousgaming.sdk.json
31
+ ├── capabilities/stock/capability.sdk.json
32
+ ├── engine/engine.sdk.json
33
+ └── ui/ui.sdk.json
34
+ ```
35
+
36
+ Edit the file by hand, or through the CLI:
37
+
38
+ ```bash
39
+ pnpm run sdk update package --patch '[{ "op": "replace", "path": "/maxTicks", "value": 20 }]'
40
+ ```
41
+
42
+ ## Engines and UIs
43
+
44
+ A game holds one engine and one game UI: whichever ones the package declares. There is nothing to list. Order the simulation's steps by the order you call them inside the engine's `Run`.
45
+
46
+ ## Configuring them
47
+
48
+ A game's parameters live in settings resources, `settings/<name>/settings.sdk.json`, not in the manifest. Each property declares a default, and the default is the game's own value: to change it, edit the settings resource. A resource reads the settings it lists in its `settings`:
49
+
50
+ ```json
51
+ {
52
+ "definition": {
53
+ "reads": ["stock"],
54
+ "writes": ["stock"],
55
+ "settings": ["engine"]
56
+ }
57
+ }
58
+ ```
59
+
60
+ A scenario or the facilitator's setup screen overrides settings by identifier, and nothing else does. The manifest has no `configuration` block, and the build refuses one. See [Settings](./settings.md).
61
+
62
+ The manifest's `maxTicks`, `teams` and `waitingRoom` are the exception, because every game has them: they are the game's own values for the built-in `game` settings, which a scenario or setup overrides under `game` like any other. Only `teams.mode` is fixed, since nothing can change a game's interaction mode. `gameSettingsConstraints` narrows or locks the rest, for example to four to ten rounds (see [Narrow or lock the game settings](./settings.md#narrow-or-lock-the-game-settings)). See [The game settings](./settings.md#the-game-settings).
63
+
64
+ ## Adding setup and a scenario editor
65
+
66
+ Two optional slots edit a game's settings. Each is a slot, so nothing in the manifest names it:
67
+
68
+ - `setup/`: a short form at session creation, writing that session's overrides only. Every session of a game with a setup slot starts at its configuration step, with the setup screen open and participants waiting, until the facilitator presses **Play**; switching scenario returns it there. A session of a game without one starts at tick 0
69
+ - `scenario_editor/`: a screen for building lasting scenarios, at any time and with no session. The host offers save-as-scenario from it, never from setup
70
+
71
+ Each lists the settings resources it edits in `configures`. A game may have either, both, or neither. Custom scenarios in a hosted runtime are switched on by `customScenarios: { "supported": true }`, not by the editor: an agent can author a scenario from well-described settings schemas alone.
72
+
73
+ See [Building a setup screen](./setup.md) and [Building a scenario editor](./scenario-editor.md).
74
+
75
+ ## Multi-language support
76
+
77
+ Add localisation to your game:
78
+
79
+ ```json
80
+ {
81
+ "identifier": "com.example.production",
82
+ "supportedLocales": ["en", "es", "fr"],
83
+ "locales": {
84
+ "en": { "name": "Production Game" },
85
+ "es": { "name": "Simulación de Producción" },
86
+ "fr": { "name": "Game de Production" }
87
+ },
88
+ "maxTicks": 10
89
+ }
90
+ ```
91
+
92
+ The facilitator switches the session language with the locale select in the console's dev drawer; the choice applies to every participant.
93
+
94
+ ## Goal-based games
95
+
96
+ A game can end when a condition is met rather than after a fixed count: leave out `maxTicks` and return `complete: true` from the engine's `Run` (or from a tick controller). See [Game completion](./tick-controller.md#game-completion) for the reference, and [End a game when a condition is met](../recipes/end-game-on-condition.md) for the steps.
97
+
98
+ ## Host controls
99
+
100
+ **The Play control is the host's.** It is always rendered on the configuration
101
+ step and a game cannot hide it. A setup screen may add a finish control of
102
+ its own with the [`requestPlay` callback](./setup.md#starting-play-from-your-own-control),
103
+ which does the same thing, but it is an addition, never a replacement, so a bug
104
+ in a game's own control cannot leave a facilitator with no way out of
105
+ configuration.
106
+
107
+ **Tick controls are the game's own.** There is no built-in next tick button: use the [`requestTick` callback](./ui.md#requesting-a-tick) in a game or session UI to advance the game. The console's dev drawer keeps a developer-only **Tick** button for testing.
108
+
109
+ **Tip**: To finish configuration from inside your own form, call `requestPlay` from your [setup screen](./setup.md). The host's Play control stays alongside it.
110
+
111
+ ## Full example game
112
+
113
+ Here's a complete game example:
114
+
115
+ ```json
116
+ {
117
+ "identifier": "com.example.retail",
118
+ "supportedLocales": ["en", "es"],
119
+ "locales": {
120
+ "en": { "name": "Retail Business Game" },
121
+ "es": { "name": "Simulación de Negocio Minorista" }
122
+ },
123
+ "maxTicks": 52,
124
+ "teams": { "mode": "independent", "min": 2, "max": 6 }
125
+ }
126
+ ```
127
+
128
+ ## Testing games
129
+
130
+ Start the dev server to test your game:
131
+
132
+ ```bash
133
+ pnpm run sdk dev
134
+ ```
135
+
136
+ Open http://localhost:3000 to view your game. You can:
137
+
138
+ - Step through ticks
139
+ - Inspect context state
140
+ - Undo/redo changes
141
+ - Switch languages
142
+
143
+ ## Updating games
144
+
145
+ `update package` takes no identifier of its own, and a patch's paths start at the manifest:
146
+
147
+ ```bash
148
+ pnpm run sdk update package --patch '[{ "op": "replace", "path": "/maxTicks", "value": 20 }]'
149
+ ```
150
+
151
+ ## Best practices
152
+
153
+ 1. **Name clearly**: use descriptive package identifiers, since the game takes its name from the package
154
+ - ✅ `com.example.retailbusinessgame`
155
+ - ❌ `com.example.sim1`
156
+
157
+ 2. **Reasonable tick count**: balance educational value and execution time
158
+ - ✅ 10-52 ticks for most games
159
+ - ❌ 1000+ ticks
160
+
161
+ 3. **Organise resources**: group related variables into one capability
162
+ - ✅ Call the engine's steps in the order their data flows
163
+ - ❌ Reading a value a later step writes
164
+
165
+ 4. **Test before deploying**: use the dev server to validate everything works
166
+ - ✅ Run through all ticks, check engine behaviour
167
+ - ❌ Deploy without testing
168
+
169
+ 5. **Support languages**: add localisation from the start
170
+ - ✅ Include English, Spanish, French
171
+ - ❌ Only English
172
+
173
+ ## Next steps
174
+
175
+ - [Getting started](../getting-started.md): test your game
176
+ - [Building user interfaces](./ui.md): build custom UIs
@@ -0,0 +1,94 @@
1
+ # Global settings
2
+
3
+ The global settings file lets you store API keys and defaults once, in a single user-owned file, so every project picks them up. It is the convenient default for the credentials the SDK needs: ElevenLabs and the Serious Gaming platform.
4
+
5
+ Without it, each new shell needs the keys re-exported, and project-level `.env` files are easy to leak into git. The global file lives outside any project, so it is shared across every game you work on.
6
+
7
+ ## Location
8
+
9
+ The file lives at:
10
+
11
+ ```
12
+ ~/.seriousgaming-sdk/config.json
13
+ ```
14
+
15
+ This resolves correctly on every platform via the home directory, with no platform branching:
16
+
17
+ - macOS: `~/.seriousgaming-sdk/config.json`
18
+ - Linux: `~/.seriousgaming-sdk/config.json`
19
+ - Windows: `C:\Users\<user>\.seriousgaming-sdk\config.json`
20
+
21
+ To use a different location, set `SERIOUSGAMING_SDK_CONFIG` to an absolute path:
22
+
23
+ ```bash
24
+ export SERIOUSGAMING_SDK_CONFIG=/path/to/my-settings.json
25
+ ```
26
+
27
+ Run `pnpm run sdk config path` to print the resolved location.
28
+
29
+ ## File shape
30
+
31
+ ```json
32
+ {
33
+ "elevenLabs": { "apiKey": "..." },
34
+ "platform": {
35
+ "apiHost": "https://app.businesssimulations.com",
36
+ "apiKey": "sg_...",
37
+ "defaultPublisher": "..."
38
+ }
39
+ }
40
+ ```
41
+
42
+ Every field is optional. Set only what you need.
43
+
44
+ ## Managing settings from the CLI
45
+
46
+ You do not have to edit the file by hand. The `config` commands manage it for you:
47
+
48
+ ```bash
49
+ # Set a value (creates the file if needed)
50
+ pnpm run sdk config set platform.apiKey sg_...
51
+
52
+ # Read a value
53
+ pnpm run sdk config get platform.apiKey
54
+
55
+ # Print the resolved file path
56
+ pnpm run sdk config path
57
+ ```
58
+
59
+ The available keys are:
60
+
61
+ - `elevenLabs.apiKey`
62
+ - `platform.apiHost`, `platform.apiKey`, `platform.defaultPublisher`
63
+
64
+ `config set` replaces whatever the key held before, and says so when it does. Check with `config get` before you set a key you are only trying to confirm, and be wary of pasting an example value from these docs over a real one.
65
+
66
+ Only `config set` writes to the file. `config get` and `config path` never change it.
67
+
68
+ If the file exists but is not valid JSON, `config set` refuses to write rather than replacing it with just the key you asked for. Fix or remove the file, then run the command again.
69
+
70
+ ## Precedence
71
+
72
+ The global settings file is the lowest-priority source. From lowest to highest, the SDK merges:
73
+
74
+ 1. Global settings file (`~/.seriousgaming-sdk/config.json`)
75
+ 2. Project `.env` file
76
+ 3. Environment variables
77
+ 4. CLI flags
78
+
79
+ So a value in your shell environment, a project `.env`, or a `--flag` always wins over the global file. This keeps CI and one-off shells in control: the file is a convenience default, never an override.
80
+
81
+ For example, with `platform.apiKey` set in the global file, running a command in a shell that also exports `SERIOUSGAMING_API_KEY` uses the environment value, not the file.
82
+
83
+ ## Security
84
+
85
+ The file is stored in plaintext and is not encrypted. Treat it like any other secrets file:
86
+
87
+ - On macOS and Linux, the SDK creates it with owner-only permissions. You can confirm or re-apply this with `chmod 600 ~/.seriousgaming-sdk/config.json`
88
+ - On Windows, restrict access with a restrictive ACL so only your user account can read it
89
+ - Never commit the file to git. It lives outside your project directory by default, so it is not picked up by a project's version control unless you move it
90
+
91
+ ## Related
92
+
93
+ - [LLM integration](llm-integration.md)
94
+ - [Speech generation](speech-generation.md)