@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.
- package/LICENSE +93 -0
- package/README.md +36 -2
- package/dist/bundling/logic-exports.d.ts +34 -0
- package/dist/bundling/node-builtins.d.ts +21 -0
- package/dist/bundling/transpiler.d.ts +7 -0
- package/dist/cli.js +27368 -0
- package/dist/client/dev-server.d.ts +59 -0
- package/dist/client/index.d.ts +28 -0
- package/dist/client/stub-services.d.ts +32 -0
- package/dist/client/test-session.d.ts +195 -0
- package/dist/client.js +16884 -0
- package/dist/core/asset-reference-translator.d.ts +46 -0
- package/dist/core/clock.d.ts +57 -0
- package/dist/core/faults.d.ts +65 -0
- package/dist/core/game-settings.d.ts +65 -0
- package/dist/core/history.d.ts +89 -0
- package/dist/core/listen-with-fallback.d.ts +30 -0
- package/dist/core/mutation-queue.d.ts +61 -0
- package/dist/core/package-scenario.d.ts +89 -0
- package/dist/core/participant-registry.d.ts +109 -0
- package/dist/core/pending-changes.d.ts +14 -0
- package/dist/core/protocol.d.ts +83 -0
- package/dist/core/scenario-drafts.d.ts +32 -0
- package/dist/core/session-endpoints.d.ts +108 -0
- package/dist/core/session-gates.d.ts +75 -0
- package/dist/core/session-host.d.ts +94 -0
- package/dist/core/session-refusals.d.ts +56 -0
- package/dist/core/session-rewind.d.ts +32 -0
- package/dist/core/session-roster.d.ts +128 -0
- package/dist/core/session-surfaces.d.ts +32 -0
- package/dist/core/session-ticks.d.ts +18 -0
- package/dist/core/session-timers.d.ts +84 -0
- package/dist/core/session-writes.d.ts +40 -0
- package/dist/core/session.d.ts +385 -0
- package/dist/core/settings-locks.d.ts +22 -0
- package/dist/core/team-names.d.ts +52 -0
- package/dist/core/team-state-store.d.ts +209 -0
- package/dist/core/tick-timer.d.ts +75 -0
- package/dist/dev/blob-links.d.ts +10 -0
- package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
- package/dist/dev/console/index.html +12 -0
- package/dist/dev/console-path.d.ts +19 -0
- package/dist/dev/lockfile.d.ts +124 -0
- package/dist/dev/package-watcher.d.ts +21 -0
- package/dist/dev/routes.d.ts +67 -0
- package/dist/dev/shell.d.ts +260 -0
- package/dist/dev/surfaces.d.ts +41 -0
- package/dist/dev/tools-identity.d.ts +23 -0
- package/dist/dev/view/batch.d.ts +172 -0
- package/dist/dev/view/commands.d.ts +75 -0
- package/dist/dev/view/route.d.ts +58 -0
- package/dist/dev/view/view.d.ts +78 -0
- package/dist/dev/view/viewer.d.ts +32 -0
- package/dist/discovery/config.d.ts +9 -0
- package/dist/discovery/manifest-paths.d.ts +21 -0
- package/dist/discovery/removed-fields.d.ts +90 -0
- package/dist/docs/api/deck.md +386 -0
- package/dist/docs/api/index.md +67 -0
- package/dist/docs/api/sdk-commands.md +286 -0
- package/dist/docs/api/sdk-e2e.md +433 -0
- package/dist/docs/api/sdk-projection.md +207 -0
- package/dist/docs/api/sdk-testing.md +214 -0
- package/dist/docs/api/sdk-tools-client.md +560 -0
- package/dist/docs/api/sdk-ui-testing.md +231 -0
- package/dist/docs/api/sdk-ui.md +1269 -0
- package/dist/docs/api/sdk.md +2140 -0
- package/dist/docs/changelog-0.x.md +1156 -0
- package/dist/docs/changelog.md +236 -0
- package/dist/docs/cli-and-dev-server.md +217 -0
- package/dist/docs/cli-error-codes.md +154 -0
- package/dist/docs/cli-reference.md +2488 -0
- package/dist/docs/cli-simulation-testing.md +222 -0
- package/dist/docs/concepts/index.md +46 -0
- package/dist/docs/concepts/terminology.md +322 -0
- package/dist/docs/environment-variables.md +102 -0
- package/dist/docs/getting-started.md +145 -0
- package/dist/docs/guides/ai-facilitation.md +220 -0
- package/dist/docs/guides/capabilities.md +460 -0
- package/dist/docs/guides/endpoints.md +335 -0
- package/dist/docs/guides/engine-testing.md +477 -0
- package/dist/docs/guides/engine.md +527 -0
- package/dist/docs/guides/file-handling.md +405 -0
- package/dist/docs/guides/games.md +176 -0
- package/dist/docs/guides/global-settings.md +94 -0
- package/dist/docs/guides/guide.md +123 -0
- package/dist/docs/guides/hooks.md +183 -0
- package/dist/docs/guides/index.md +51 -0
- package/dist/docs/guides/internationalization.md +288 -0
- package/dist/docs/guides/llm-integration.md +635 -0
- package/dist/docs/guides/multi-team-games.md +586 -0
- package/dist/docs/guides/patterns.md +391 -0
- package/dist/docs/guides/presentation.md +196 -0
- package/dist/docs/guides/production-constraints.md +130 -0
- package/dist/docs/guides/scenario-editor.md +143 -0
- package/dist/docs/guides/scenarios.md +515 -0
- package/dist/docs/guides/session.md +972 -0
- package/dist/docs/guides/settings.md +458 -0
- package/dist/docs/guides/setup.md +193 -0
- package/dist/docs/guides/speech-generation.md +233 -0
- package/dist/docs/guides/starter-game.md +164 -0
- package/dist/docs/guides/testing.md +575 -0
- package/dist/docs/guides/tick-controller.md +569 -0
- package/dist/docs/guides/type-safety.md +516 -0
- package/dist/docs/guides/ui.md +1389 -0
- package/dist/docs/guides/upgrading.md +636 -0
- package/dist/docs/guides/validators.md +217 -0
- package/dist/docs/guides/waiting-room.md +244 -0
- package/dist/docs/index.md +41 -0
- package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
- package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
- package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
- package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
- package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
- package/dist/docs/recipes/create-capability.md +66 -0
- package/dist/docs/recipes/create-endpoint.md +127 -0
- package/dist/docs/recipes/create-engine.md +111 -0
- package/dist/docs/recipes/create-game.md +70 -0
- package/dist/docs/recipes/create-guide.md +75 -0
- package/dist/docs/recipes/create-hook.md +80 -0
- package/dist/docs/recipes/create-presentation.md +142 -0
- package/dist/docs/recipes/create-scenario-editor.md +102 -0
- package/dist/docs/recipes/create-scenario.md +98 -0
- package/dist/docs/recipes/create-session-ui.md +116 -0
- package/dist/docs/recipes/create-settings.md +88 -0
- package/dist/docs/recipes/create-setup.md +115 -0
- package/dist/docs/recipes/create-tick-controller.md +65 -0
- package/dist/docs/recipes/create-user-interface.md +96 -0
- package/dist/docs/recipes/create-validator.md +67 -0
- package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
- package/dist/docs/recipes/declare-api-uses.md +67 -0
- package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
- package/dist/docs/recipes/distribute-package.md +80 -0
- package/dist/docs/recipes/e2e-testing.md +116 -0
- package/dist/docs/recipes/end-game-on-condition.md +154 -0
- package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
- package/dist/docs/recipes/index.md +86 -0
- package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
- package/dist/docs/recipes/negotiate-across-teams.md +164 -0
- package/dist/docs/recipes/observe-other-teams-state.md +119 -0
- package/dist/docs/recipes/publish-a-package.md +139 -0
- package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
- package/dist/docs/recipes/review-a-past-day.md +73 -0
- package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
- package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
- package/dist/docs/recipes/show-all-teams-participants.md +109 -0
- package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
- package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
- package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
- package/dist/docs/recipes/test-user-interface.md +95 -0
- package/dist/docs/recipes/test-with-engine.md +115 -0
- package/dist/docs/recipes/upload-and-display-files.md +148 -0
- package/dist/docs/recipes/use-request-play.md +65 -0
- package/dist/docs/recipes/use-request-tick.md +75 -0
- package/dist/docs/troubleshooting.md +816 -0
- package/dist/execution/blob-inputs.d.ts +57 -0
- package/dist/execution/bundle-reload.d.ts +42 -0
- package/dist/execution/config-resolver.d.ts +127 -0
- package/dist/execution/configuration-builder.d.ts +66 -0
- package/dist/execution/context-composition.d.ts +161 -0
- package/dist/execution/endpoint-actions.d.ts +9 -0
- package/dist/execution/endpoint-payloads.d.ts +32 -0
- package/dist/execution/endpoint-runner.d.ts +129 -0
- package/dist/execution/engine-state.d.ts +40 -0
- package/dist/execution/executor-callbacks.d.ts +56 -0
- package/dist/execution/executor.d.ts +436 -0
- package/dist/execution/gates.d.ts +45 -0
- package/dist/execution/hook-chain.d.ts +25 -0
- package/dist/execution/logic-calls.d.ts +190 -0
- package/dist/execution/logic-failure.d.ts +84 -0
- package/dist/execution/logic-loader.d.ts +54 -0
- package/dist/execution/logic-result.d.ts +76 -0
- package/dist/execution/tick-controller-call.d.ts +32 -0
- package/dist/execution/types.d.ts +48 -0
- package/dist/generation/ts-type.d.ts +39 -0
- package/dist/guide/locale.d.ts +12 -0
- package/dist/guide/video-registry.d.ts +42 -0
- package/dist/help-examples.d.ts +125 -0
- package/dist/host/adapter.d.ts +67 -0
- package/dist/host/host-channel.d.ts +222 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/protocol.d.ts +783 -0
- package/dist/host/surfaces.d.ts +42 -0
- package/dist/host/theme.d.ts +55 -0
- package/dist/host.js +583 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/schema-builder.d.ts +29 -0
- package/dist/prompts/types.d.ts +108 -0
- package/dist/prompts/validators.d.ts +32 -0
- package/dist/runtime/capability-scope.d.ts +6 -0
- package/dist/runtime/context-merge.d.ts +29 -0
- package/dist/runtime/context-projection.d.ts +41 -0
- package/dist/runtime/guarded-callbacks.d.ts +15 -0
- package/dist/runtime/index.d.ts +27 -0
- package/dist/runtime/manifest-version.d.ts +55 -0
- package/dist/runtime/tick-validation.d.ts +25 -0
- package/dist/runtime.js +346 -0
- package/dist/schema.json +1546 -0
- package/dist/server/vite-plugin.d.ts +46 -0
- package/dist/services/tts/client.d.ts +51 -0
- package/dist/services/tts/elevenlabs.d.ts +24 -0
- package/dist/services/tts/types.d.ts +44 -0
- package/dist/storage/asset-resolver.d.ts +49 -0
- package/dist/storage/blob-store.d.ts +41 -0
- package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
- package/dist/templates/endpoint/logic.ts.liquid +25 -0
- package/dist/templates/engine/init.json.liquid +7 -0
- package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
- package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
- package/dist/templates/engine/logic.test.ts.liquid +61 -0
- package/dist/templates/engine/logic.ts.liquid +32 -0
- package/dist/templates/engine/run.json.liquid +7 -0
- package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
- package/dist/templates/guide/02-preparation.md.liquid +18 -0
- package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
- package/dist/templates/hook/logic.test.ts.liquid +46 -0
- package/dist/templates/hook/logic.ts.liquid +12 -0
- package/dist/templates/package/CLAUDE.md.template +338 -0
- package/dist/templates/package/DESIGN.md.liquid +144 -0
- package/dist/templates/package/README.md.liquid +143 -0
- package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
- package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
- package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
- package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
- package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
- package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
- package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
- package/dist/templates/package/claude/settings.json.template +16 -0
- package/dist/templates/package/gitignore.template +15 -0
- package/dist/templates/package/global-setup.ts.template +21 -0
- package/dist/templates/package/globals.d.ts.template +2 -0
- package/dist/templates/package/oxfmtrc.json.template +14 -0
- package/dist/templates/package/oxlintrc.json.template +238 -0
- package/dist/templates/package/package.json.liquid +65 -0
- package/dist/templates/package/playwright.config.ts.liquid +19 -0
- package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
- package/dist/templates/package/test-setup.ts.liquid +7 -0
- package/dist/templates/package/tsconfig.json.liquid +15 -0
- package/dist/templates/package/vite.config.ts.liquid +19 -0
- package/dist/templates/package/vitest.config.ts.liquid +25 -0
- package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
- package/dist/templates/presentation/index.html.liquid +20 -0
- package/dist/templates/presentation/src/App.tsx.liquid +49 -0
- package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
- package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
- package/dist/templates/presentation/src/main.tsx.liquid +14 -0
- package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
- package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
- package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
- package/dist/templates/session/src/App.test.tsx.liquid +60 -0
- package/dist/templates/session/src/App.tsx.liquid +128 -0
- package/dist/templates/session/src/i18n.ts.liquid +53 -0
- package/dist/templates/session/src/main.tsx.liquid +48 -0
- package/dist/templates/session/src/theme.ts.liquid +46 -0
- package/dist/templates/settings-editor/index.html.liquid +12 -0
- package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
- package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
- package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
- package/dist/templates/setup/src/App.tsx.liquid +102 -0
- package/dist/templates/setup/src/i18n.ts.liquid +47 -0
- package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
- package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
- package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
- package/dist/templates/starter-game/engine/defaults.ts +81 -0
- package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
- package/dist/templates/starter-game/engine/logic.test.ts +221 -0
- package/dist/templates/starter-game/engine/logic.ts +149 -0
- package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
- package/dist/templates/starter-game/engine/types.ts +139 -0
- package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
- package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
- package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
- package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
- package/dist/templates/starter-game/guide/guide.test.ts +108 -0
- package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
- package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
- package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
- package/dist/templates/starter-game/package.json +68 -0
- package/dist/templates/starter-game/playwright.config.ts +19 -0
- package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
- package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
- package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
- package/dist/templates/starter-game/session/defaults.ts +80 -0
- package/dist/templates/starter-game/session/index.html +12 -0
- package/dist/templates/starter-game/session/session.sdk.json +11 -0
- package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
- package/dist/templates/starter-game/session/src/App.tsx +309 -0
- package/dist/templates/starter-game/session/src/Section.tsx +37 -0
- package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
- package/dist/templates/starter-game/session/src/format.ts +8 -0
- package/dist/templates/starter-game/session/src/i18n.ts +51 -0
- package/dist/templates/starter-game/session/src/main.tsx +48 -0
- package/dist/templates/starter-game/session/src/theme.ts +45 -0
- package/dist/templates/starter-game/session/types.ts +135 -0
- package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
- package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
- package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
- package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
- package/dist/templates/starter-game/test-setup.ts +7 -0
- package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
- package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
- package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
- package/dist/templates/starter-game/tsconfig.json +19 -0
- package/dist/templates/starter-game/ui/defaults.ts +60 -0
- package/dist/templates/starter-game/ui/index.html +12 -0
- package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
- package/dist/templates/starter-game/ui/src/App.tsx +175 -0
- package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
- package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
- package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
- package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
- package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
- package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
- package/dist/templates/starter-game/ui/src/format.ts +12 -0
- package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
- package/dist/templates/starter-game/ui/src/main.tsx +48 -0
- package/dist/templates/starter-game/ui/src/menu.ts +18 -0
- package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
- package/dist/templates/starter-game/ui/src/theme.ts +45 -0
- package/dist/templates/starter-game/ui/src/writes.ts +43 -0
- package/dist/templates/starter-game/ui/types.ts +137 -0
- package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
- package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
- package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
- package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
- package/dist/templates/starter-game/vite.config.ts +19 -0
- package/dist/templates/starter-game/vitest.config.ts +15 -0
- package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
- package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
- package/dist/templates/starter-game/waiting_room/index.html +12 -0
- package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
- package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
- package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
- package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
- package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
- package/dist/templates/starter-game/waiting_room/types.ts +59 -0
- package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
- package/dist/templates/tick-controller/logic.ts.liquid +8 -0
- package/dist/templates/ui/index.html.liquid +12 -0
- package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
- package/dist/templates/ui/src/App.tsx.liquid +103 -0
- package/dist/templates/ui/src/i18n.ts.liquid +47 -0
- package/dist/templates/ui/src/main.tsx.liquid +48 -0
- package/dist/templates/ui/src/theme.ts.liquid +46 -0
- package/dist/templates/validator/logic.test.ts.liquid +42 -0
- package/dist/templates/validator/logic.ts.liquid +21 -0
- package/dist/templates/waiting-room/index.html.liquid +12 -0
- package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
- package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
- package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
- package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
- package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
- package/dist/templating/resource-metadata.d.ts +131 -0
- package/dist/types.d.ts +14 -0
- package/dist/utils/atomic-write.d.ts +40 -0
- package/dist/utils/cli-error.d.ts +64 -0
- package/dist/utils/document-parser-client.d.ts +20 -0
- package/dist/utils/env-loader.d.ts +40 -0
- package/dist/utils/error-codes.d.ts +335 -0
- package/dist/utils/errors.d.ts +19 -0
- package/dist/utils/global-settings.d.ts +65 -0
- package/dist/utils/llm-client.d.ts +34 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/output.d.ts +109 -0
- package/dist/utils/platform.d.ts +14 -0
- package/dist/utils/prompt.d.ts +36 -0
- package/dist/utils/real-path.d.ts +6 -0
- package/dist/utils/schema-properties.d.ts +35 -0
- package/dist/validation/ajv.d.ts +17 -0
- package/dist/validation/facilitator-guide-validator.d.ts +27 -0
- package/dist/validation/findings.d.ts +40 -0
- package/dist/validation/game-constraints.d.ts +10 -0
- package/dist/validation/game-validator.d.ts +34 -0
- package/dist/validation/package-validation.d.ts +41 -0
- package/dist/validation/package-validator.d.ts +19 -0
- package/dist/validation/resource-validator.d.ts +79 -0
- package/dist/validation/schema-defaults-validator.d.ts +29 -0
- package/dist/validation/schema-validation.d.ts +115 -0
- package/dist/validation/semver.d.ts +9 -0
- package/package.json +122 -3
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Facilitator guides
|
|
2
|
+
|
|
3
|
+
A facilitator guide bundles the run-sheet for a simulation into the package itself: markdown chapters and images that the host renders natively on the facilitator's page. Bundling means guide content can never drift out of sync with the simulation version it ships with, and the facilitator manages everything from one place.
|
|
4
|
+
|
|
5
|
+
Guides are content as data, not UI bundles: learning designers author markdown, the host renders it. A game holds one guide; a one-page cheat sheet is a chapter of it.
|
|
6
|
+
|
|
7
|
+
## Resource shape
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
guide/
|
|
11
|
+
├── guide.sdk.json
|
|
12
|
+
├── content/
|
|
13
|
+
│ ├── en/
|
|
14
|
+
│ │ ├── 01-overview.md
|
|
15
|
+
│ │ └── 02-running-the-game.md
|
|
16
|
+
│ └── de/
|
|
17
|
+
│ ├── 01-overview.md
|
|
18
|
+
│ └── 02-running-the-game.md
|
|
19
|
+
└── assets/
|
|
20
|
+
└── market-diagram.png
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The bundle file declares a `locales` map; the per-locale `name` is the title the host renders:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"definition": {
|
|
28
|
+
"locales": {
|
|
29
|
+
"en": { "name": "Facilitator guide" },
|
|
30
|
+
"de": { "name": "Moderationsleitfaden" }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"rootDirectory": "."
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Content rules
|
|
38
|
+
|
|
39
|
+
- Markdown files are chapters, ordered by file name. Number them (`01-`, `02-`, …) to control the order
|
|
40
|
+
- File names are identical across locales; only the locale directory differs
|
|
41
|
+
- Headings produce the nested table of contents: chapter titles (H1) plus H2 to H3. End a heading with `{-}` to omit it
|
|
42
|
+
- Images use relative paths into the shared `assets/` directory (for example `../../assets/market-diagram.png` from a chapter). Localised images are simply different files referenced from each locale's markdown
|
|
43
|
+
|
|
44
|
+
## Admonitions
|
|
45
|
+
|
|
46
|
+
Six admonition types are available as container directives. The standard three:
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
:::note
|
|
50
|
+
A setup screen overrides these defaults.
|
|
51
|
+
:::
|
|
52
|
+
|
|
53
|
+
:::warning
|
|
54
|
+
Do not reveal the demand curve before round two.
|
|
55
|
+
:::
|
|
56
|
+
|
|
57
|
+
:::tip
|
|
58
|
+
Tie at least one in-game moment back to each learning objective.
|
|
59
|
+
:::
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
And three facilitation-specific types, following the 'DO/SAY' labelling convention from facilitation practice:
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
:::say
|
|
66
|
+
Welcome everyone. Today you are running a company for three simulated years.
|
|
67
|
+
:::
|
|
68
|
+
|
|
69
|
+
:::do
|
|
70
|
+
Form the teams and hand each team its device.
|
|
71
|
+
:::
|
|
72
|
+
|
|
73
|
+
:::timing
|
|
74
|
+
Keep this briefing under 10 minutes.
|
|
75
|
+
:::
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Unknown admonition types render as an inert error block so typos surface immediately.
|
|
79
|
+
|
|
80
|
+
## Video embeds
|
|
81
|
+
|
|
82
|
+
A video embed is a fenced code block with language `video`, validated against a closed provider registry (YouTube only in v1):
|
|
83
|
+
|
|
84
|
+
````markdown
|
|
85
|
+
```video
|
|
86
|
+
provider: youtube
|
|
87
|
+
id: dQw4w9WgXcQ
|
|
88
|
+
```
|
|
89
|
+
````
|
|
90
|
+
|
|
91
|
+
The id must match YouTube's 11-character format. Unknown providers or malformed blocks render as an inert error block, never a passthrough. The dev server renders the embed from `youtube-nocookie.com` in a sandboxed iframe.
|
|
92
|
+
|
|
93
|
+
## Rendering and safety
|
|
94
|
+
|
|
95
|
+
The renderer is default-deny:
|
|
96
|
+
|
|
97
|
+
- Raw HTML is escaped and shown as text, never executed
|
|
98
|
+
- Link schemes are filtered; `javascript:` and friends are stripped
|
|
99
|
+
- Relative references must stay inside the guide's own content and assets
|
|
100
|
+
|
|
101
|
+
Stick to markdown, admonitions, and video blocks; anything else is treated as content, not markup.
|
|
102
|
+
|
|
103
|
+
## Localisation
|
|
104
|
+
|
|
105
|
+
The guide gets its own locale picker on the facilitator's page, defaulting to the session locale. It is guide-local: changing it never affects what participants see.
|
|
106
|
+
|
|
107
|
+
Chapters fall back per file: a chapter missing from the selected locale is served from the reference locale ('en' when declared, otherwise the first declared locale) with a visible 'not yet translated' notice. Translate chapter by chapter and the guide stays usable throughout.
|
|
108
|
+
|
|
109
|
+
`pnpm run sdk build --validate-only` warns when a declared locale is missing chapters present in the reference locale, and fails on malformed video blocks or unresolvable image references.
|
|
110
|
+
|
|
111
|
+
## Printing
|
|
112
|
+
|
|
113
|
+
The guide page carries a print stylesheet: the header, table of contents and pickers are hidden and the content fills the page, so cheat sheets can still live on paper next to the laptop. Use the print button on the guide page, or open `/guide` in its own tab and print from there.
|
|
114
|
+
|
|
115
|
+
## Writing a guide worth shipping
|
|
116
|
+
|
|
117
|
+
The scaffold (`pnpm run sdk new guide`) generates a recommended skeleton: a front-loaded at-a-glance summary and preparation checklist, then per-phase sections. The test of a good guide: could a facilitator new to this simulation run the session from this guide alone? If a step relies on knowledge that is only in the designer's head, write it down.
|
|
118
|
+
|
|
119
|
+
## See also
|
|
120
|
+
|
|
121
|
+
- [Create a Facilitator Guide](../recipes/create-guide.md) - step-by-step recipe
|
|
122
|
+
- [Internationalisation](internationalization.md) - locale rules across the SDK
|
|
123
|
+
- [Scenarios](scenarios.md) - the other data-only resource type
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
A hook is logic that runs when the runtime emits a lifecycle signal, such as a participant joining a team. It runs outside the tick loop, after the runtime has applied the change, and can write the capabilities it declares.
|
|
4
|
+
|
|
5
|
+
## The model
|
|
6
|
+
|
|
7
|
+
The engine runs inside the tick loop: it takes a context, returns what it writes, and does so only when the loop ticks. That fits everything shaped like 'every tick, do X', but not what happens between ticks, such as a participant joining or a facilitator moving someone to another team. There is no tick boundary to bind engine logic to, so an engine either misses those events or has to poll for them.
|
|
8
|
+
|
|
9
|
+
The runtime knows exactly when each lifecycle event happens, because it caused it. So it emits a signal, the hooks subscribed to that signal run, and their result is folded back into the game's context. Think of a hook as a reaction to a runtime announcement, the way the engine is a tick-time computation:
|
|
10
|
+
|
|
11
|
+
- The runtime owns the signals, and decides when to emit them
|
|
12
|
+
- A hook subscribes to exactly one signal, and the signal's payload is part of its type, so there is nothing to check at run time
|
|
13
|
+
- A hook runs after the change has been applied. It can record, react to, or augment the change; it can never veto it
|
|
14
|
+
- Several hooks on one signal run in directory-name order, each receiving the previous one's output, so the chain composes
|
|
15
|
+
|
|
16
|
+
A hook fires on a signal the runtime emits, never on a condition you define over context. A condition over context is already expressible in the engine: check it at the start of `Run` and return early. What the engine cannot express is 'a participant joined the team', and hooks fill exactly that gap.
|
|
17
|
+
|
|
18
|
+
### When to use a hook (versus the engine)
|
|
19
|
+
|
|
20
|
+
- **Engine**: the work belongs in the tick loop. It runs every tick and depends on tick-level orchestration
|
|
21
|
+
- **Hook**: the work is triggered by a lifecycle event with no tick correspondence, and needs to read or write game context. There is no notion of 'every tick' for a hook: it fires only when its signal does
|
|
22
|
+
|
|
23
|
+
## Available signals
|
|
24
|
+
|
|
25
|
+
| Signal | Fires when | Payload |
|
|
26
|
+
| ----------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
27
|
+
| `participantJoin` | A new participant record is created | `{ participant: { id, teamId, name, index, connected } }` |
|
|
28
|
+
| `participantMove` | A facilitator reassigns a participant to another team. Dispatched **once per affected team** | `{ participant, fromTeamId, toTeamId, phase }` |
|
|
29
|
+
|
|
30
|
+
The `HookSignal` type lists every signal, and each hook's generated `types.ts` pins its own signal and payload.
|
|
31
|
+
|
|
32
|
+
## Creating a hook
|
|
33
|
+
|
|
34
|
+
This creates an `assignroles` hook, which gives each new participant a role. It reads and writes a `roles` capability whose `Assignments` map is keyed by participant id:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm run sdk new hook '{
|
|
38
|
+
"definition": {
|
|
39
|
+
"identifier": "assignroles",
|
|
40
|
+
"signal": "participantJoin",
|
|
41
|
+
"reads": ["roles"],
|
|
42
|
+
"writes": ["roles"]
|
|
43
|
+
},
|
|
44
|
+
"logic": {
|
|
45
|
+
"directory": "logic",
|
|
46
|
+
"entrypoint": "logic.ts"
|
|
47
|
+
},
|
|
48
|
+
"rootDirectory": "."
|
|
49
|
+
}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`identifier` names the directory the command creates; it is not written into `hook.sdk.json`, because a resource is named by its directory. Declare `reads` and `writes` here (or edit them in later): without them the hook is handed no `roles` at all, its generated `Context` is empty, and the logic below fails to type-check.
|
|
53
|
+
|
|
54
|
+
This produces:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
hooks/assignroles/
|
|
58
|
+
├── hook.sdk.json # Definition (signal, reads, writes, settings)
|
|
59
|
+
├── logic/
|
|
60
|
+
│ ├── logic.ts # Run function
|
|
61
|
+
│ └── logic.test.ts
|
|
62
|
+
├── defaults.ts # Generated: the starting context and settings, for tests
|
|
63
|
+
└── types.ts # Generated: pins the signal as a literal
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Wiring it to a game
|
|
67
|
+
|
|
68
|
+
Nothing to do: a package declares whatever is on disk, and a game runs whatever its package declares, so the directory `pnpm run sdk new hook` created is the whole of the wiring.
|
|
69
|
+
|
|
70
|
+
Where several hooks subscribe to the same signal, they run in directory-name order, so number the directories if the order matters.
|
|
71
|
+
|
|
72
|
+
## Writing the logic
|
|
73
|
+
|
|
74
|
+
The hook's logic is a single `Run` function in `logic/logic.ts`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import type { HookFunction } from '../types';
|
|
78
|
+
|
|
79
|
+
export const Run: HookFunction = async ({ context, settings, callbacks, participant }) => {
|
|
80
|
+
const roles = context.roles?.Assignments ?? {};
|
|
81
|
+
return {
|
|
82
|
+
capabilities: {
|
|
83
|
+
...context,
|
|
84
|
+
roles: {
|
|
85
|
+
Assignments: {
|
|
86
|
+
...roles,
|
|
87
|
+
[participant.id]: 'observer',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The signal-specific payload (here `participant`) is spread alongside the standard `context`, `settings`, `meta`, `participants` and `callbacks` fields. Each signal surfaces its own fields the same way.
|
|
96
|
+
|
|
97
|
+
`participants` is the roster after the change the hook reports, a read-only copy taken when the dispatch started: the team's participants on a `participantJoin`, and every participant in the session on a `participantMove`, so the departure and the arrival read the same list. Read it rather than keeping a roster in context. The `assignroles` hook uses it to hand a newcomer the first role nobody still on the team holds:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
export const Run: HookFunction = async ({ context, settings, participant, participants }) => {
|
|
101
|
+
const existing = context.roles?.Assignments ?? {};
|
|
102
|
+
const held = new Set(
|
|
103
|
+
participants.filter((p) => p.id !== participant.id).map((p) => existing[p.id])
|
|
104
|
+
);
|
|
105
|
+
const role = settings.assignroles.Roles.find((candidate) => !held.has(candidate));
|
|
106
|
+
// ...
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`meta.now` is when the dispatch started, in milliseconds since the epoch. Use it rather than `Date.now()` to stamp a join.
|
|
111
|
+
|
|
112
|
+
The generated `types.ts` pins the signal as a literal, so destructuring is type-safe without restating the signal:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// hooks/assignroles/types.ts (auto-generated)
|
|
116
|
+
import type { LogicCallbacks } from '@seriousgaming/sdk';
|
|
117
|
+
export type Callbacks = LogicCallbacks;
|
|
118
|
+
|
|
119
|
+
import type { HookFunction as SDKHookFunction } from '@seriousgaming/sdk';
|
|
120
|
+
export type HookFunction = SDKHookFunction<'participantJoin', Context, Settings, Callbacks>;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Reads and writes
|
|
124
|
+
|
|
125
|
+
Hooks declare `reads` and `writes` exactly like the engine, and the same discipline applies: a hook may only read from capabilities listed under `reads`, and may only write to capabilities listed under `writes`. The generated `Context` type reflects only those capabilities.
|
|
126
|
+
|
|
127
|
+
A hook removes a key the way the engine does, by naming its path in `deletes` beside `capabilities`. A `participantMove` hook clearing the departing participant's role, for example:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
return { capabilities: context, deletes: [['roles', 'Assignments', participant.id]] };
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Each path must sit under a capability in the hook's `writes`. See [Removing a key](./engine.md#removing-a-key) for the rules.
|
|
134
|
+
|
|
135
|
+
A hook that calls `generateText` or another gated callback lists it in `uses`, like any logic resource (see [Declare the APIs a resource uses](./llm-integration.md#declare-the-apis-a-resource-uses)).
|
|
136
|
+
|
|
137
|
+
## Order of operations
|
|
138
|
+
|
|
139
|
+
For every signal, the runtime:
|
|
140
|
+
|
|
141
|
+
1. Applies the lifecycle change (for example, adds a participant to the registry)
|
|
142
|
+
2. Emits the signal
|
|
143
|
+
3. Looks up the hooks subscribed to that signal in the current game
|
|
144
|
+
4. Runs them in directory-name order, threading context through
|
|
145
|
+
5. Persists the resulting context
|
|
146
|
+
|
|
147
|
+
So for `participantJoin` the participant is in the registry by the time the hook runs, and it is safe to assume the joiner exists. Hooks can mutate context to record, react to, or augment the change; they cannot prevent it.
|
|
148
|
+
|
|
149
|
+
### `participantMove` fires twice
|
|
150
|
+
|
|
151
|
+
A move touches two teams, and a hook writes one team's context, so the signal is dispatched once against each: the source team with `phase: 'departure'`, then the destination with `phase: 'arrival'`. Departure completes and persists before arrival starts.
|
|
152
|
+
|
|
153
|
+
Read `phase` rather than comparing ids. The change is already applied by the time either invocation runs, so `participant.teamId` reads as the destination on both and cannot tell them apart.
|
|
154
|
+
|
|
155
|
+
There is no channel between the two invocations: whatever the departure hook returns is not visible to the arrival hook. A shared-state game can carry per-participant state across through the shared store; an independent-mode game re-derives it on arrival from the payload and its own configuration.
|
|
156
|
+
|
|
157
|
+
The reason to reach for this signal is per-participant state, not the roster. `useUserInterfaceParticipants()` and the session UI's roster are pushed live and are accurate the instant a move lands, so a context-resident roster is a derived cache. What a move genuinely breaks is state your `participantJoin` hook seeded (a role, an allocation, a per-person ledger entry), which is otherwise stranded in the source team with nothing to re-seed it in the destination.
|
|
158
|
+
|
|
159
|
+
## Settings
|
|
160
|
+
|
|
161
|
+
A hook reads [settings](./settings.md) the way every other resource does: list the settings resources in the bundle's `settings`, and the hook receives exactly those as `settings`, keyed by identifier. Use them to parameterise hook behaviour without changing logic. They resolve through the same layers as the engine's, so a scenario or the facilitator's setup screen can change them.
|
|
162
|
+
|
|
163
|
+
The `assignroles` hook can read its role list from a `settings/assignroles/` resource:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"definition": {
|
|
168
|
+
"signal": "participantJoin",
|
|
169
|
+
"reads": ["roles"],
|
|
170
|
+
"writes": ["roles"],
|
|
171
|
+
"settings": ["assignroles"]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const roles = settings.assignroles.Roles;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Related
|
|
181
|
+
|
|
182
|
+
- [Create a hook recipe](../recipes/create-hook.md): step-by-step scaffolding
|
|
183
|
+
- [Engines guide](engine.md): for in-tick logic
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Guides
|
|
2
|
+
|
|
3
|
+
Each guide is the one page for a resource kind or a topic: it explains the model, then serves as the reference, with every field, every option, and the rules behind them. For how the kinds fit together, see the [core concepts](../concepts/index.md); for a single task done step by step, see the [recipes](../recipes/index.md).
|
|
4
|
+
|
|
5
|
+
## Start here
|
|
6
|
+
|
|
7
|
+
- **[A tour of the starter game](./starter-game.md)**: every directory and file `new package` gives you, and which guide goes deeper
|
|
8
|
+
- **[Common patterns](./patterns.md)**: inventory, revenue and multi-step engine patterns, with the commands and code for each
|
|
9
|
+
|
|
10
|
+
## The game and its logic
|
|
11
|
+
|
|
12
|
+
- **[Shaping games](./games.md)**: ticks, teams, locales, and the rest of `seriousgaming.sdk.json`
|
|
13
|
+
- **[Creating capabilities](./capabilities.md)**: defining the game's state
|
|
14
|
+
- **[Creating engines](./engine.md)**: the tick logic
|
|
15
|
+
- **[Hooks](./hooks.md)**: logic that runs on lifecycle signals, such as a participant joining a team
|
|
16
|
+
- **[Validators](./validators.md)**: server-side checks that reject a UI's mutation before it is applied
|
|
17
|
+
- **[Endpoints](./endpoints.md)**: server-side handlers a surface or an agent calls by identifier, their scope, writes and actions, and how a UI reaches `generateText`
|
|
18
|
+
- **[Tick controllers](./tick-controller.md)**: automatic tick timing and game completion
|
|
19
|
+
- **[Multi-team games](./multi-team-games.md)**: shared capabilities, aggregate engines, and interaction modes
|
|
20
|
+
- **[Scenarios](./scenarios.md)**: variants of a game, with settings overrides and static assets
|
|
21
|
+
- **[Settings](./settings.md)**: read-only parameters, how scenarios and the session override them, and how to set them from the CLI
|
|
22
|
+
- **[Type safety best practices](./type-safety.md)**: generated types, schemas, and avoiding `any`
|
|
23
|
+
|
|
24
|
+
## User interfaces
|
|
25
|
+
|
|
26
|
+
- **[Building user interfaces](./ui.md)**: the game UI each participant plays
|
|
27
|
+
- **[Building session UIs](./session.md)**: the facilitator's whole-session screen
|
|
28
|
+
- **[Building a setup screen](./setup.md)**: the short form a facilitator fills in at session creation
|
|
29
|
+
- **[Building a scenario editor](./scenario-editor.md)**: the screen for authoring lasting scenarios, and when a game needs one
|
|
30
|
+
- **[Building waiting room UIs](./waiting-room.md)**: the holding screen participants see while the session is held
|
|
31
|
+
- **[Production security constraints](./production-constraints.md)**: the sandbox and content security policy your UIs run under in production
|
|
32
|
+
- **[Internationalisation (i18n)](./internationalization.md)**: running a game in more than one language
|
|
33
|
+
|
|
34
|
+
## Content, files, and AI
|
|
35
|
+
|
|
36
|
+
- **[Facilitator guides](./guide.md)**: markdown facilitation content shown on the facilitator's page
|
|
37
|
+
- **[Building presentations](./presentation.md)**: the facilitator's slide deck, its deck library, resume and export
|
|
38
|
+
- **[File handling (assets and blobs)](./file-handling.md)**: uploading files from UIs, showing them, and reading them in logic
|
|
39
|
+
- **[LLM integration guide](./llm-integration.md)**: generating text through the platform LLM API
|
|
40
|
+
- **[Speech generation guide](./speech-generation.md)**: synthesising audio with `generateSpeech`
|
|
41
|
+
- **[Structuring a game for AI facilitation](./ai-facilitation.md)**: what a facilitator's AI agent can see and do, and what to write down so it behaves
|
|
42
|
+
|
|
43
|
+
## Testing
|
|
44
|
+
|
|
45
|
+
- **[Testing](./testing.md)**: unit tests for the engine and component tests for UIs
|
|
46
|
+
- **[Engine testing](./engine-testing.md)**: whole games played in-process with `createTestSession`, with no server
|
|
47
|
+
|
|
48
|
+
## Set-up and upgrades
|
|
49
|
+
|
|
50
|
+
- **[Global settings](./global-settings.md)**: API keys and defaults stored once for every project
|
|
51
|
+
- **[Upgrading](./upgrading.md)**: the steps from 0.6 to 1.0
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Internationalisation (i18n)
|
|
2
|
+
|
|
3
|
+
This guide shows you how to add multi-language support to your games.
|
|
4
|
+
|
|
5
|
+
## What you'll learn
|
|
6
|
+
|
|
7
|
+
- How to add language support to games
|
|
8
|
+
- How to localise user interfaces
|
|
9
|
+
- How to use i18next in React components
|
|
10
|
+
|
|
11
|
+
## Game localisation
|
|
12
|
+
|
|
13
|
+
Add a language to a game by patching its manifest:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm run sdk update package --patch '[
|
|
17
|
+
{"op": "replace", "path": "/supportedLocales", "value": ["en", "es", "fr"]},
|
|
18
|
+
{"op": "add", "path": "/locales/es", "value": {"name": "Simulación de negocios"}},
|
|
19
|
+
{"op": "add", "path": "/locales/fr", "value": {"name": "Simulation commerciale"}}
|
|
20
|
+
]'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The `supportedLocales` array defines which languages the game supports. The `locales` object holds the game's name in each of them, and the name in the first supported locale is the package's name.
|
|
24
|
+
|
|
25
|
+
Use `--patch` rather than a JSON definition here. A definition passed as an argument or with `--file` replaces the whole manifest, so anything it leaves out (`maxTicks`, `teams`, `waitingRoom` and the rest) is dropped. An RFC 6902 patch changes only the paths it names.
|
|
26
|
+
|
|
27
|
+
## User interface localisation
|
|
28
|
+
|
|
29
|
+
A user interface declares no localised name of its own: a game holds one, so
|
|
30
|
+
nothing labels it, and `new ui` or `update ui` refuses a `locales` field. Its own
|
|
31
|
+
strings live in `ui/src/i18n.ts` and are resolved against the locale the host
|
|
32
|
+
sends. The same holds for the session, waiting room, setup and scenario editor
|
|
33
|
+
screens.
|
|
34
|
+
|
|
35
|
+
## React i18next integration
|
|
36
|
+
|
|
37
|
+
New UIs come with i18next configured. Add translations to your UI:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// src/i18n.ts
|
|
41
|
+
import i18n from 'i18next';
|
|
42
|
+
import { initReactI18next } from 'react-i18next';
|
|
43
|
+
|
|
44
|
+
const resources = {
|
|
45
|
+
en: {
|
|
46
|
+
translation: {
|
|
47
|
+
hello: 'Hello',
|
|
48
|
+
greeting: 'Welcome to the dashboard',
|
|
49
|
+
revenue: 'Revenue',
|
|
50
|
+
sales: 'Sales',
|
|
51
|
+
stockLevel: 'Stock Level',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
es: {
|
|
55
|
+
translation: {
|
|
56
|
+
hello: 'Hola',
|
|
57
|
+
greeting: 'Bienvenido al panel',
|
|
58
|
+
revenue: 'Ingresos',
|
|
59
|
+
sales: 'Ventas',
|
|
60
|
+
stockLevel: 'Nivel de Stock',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
fr: {
|
|
64
|
+
translation: {
|
|
65
|
+
hello: 'Bonjour',
|
|
66
|
+
greeting: 'Bienvenue au tableau de bord',
|
|
67
|
+
revenue: 'Revenus',
|
|
68
|
+
sales: 'Ventes',
|
|
69
|
+
stockLevel: 'Niveau de Stock',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
i18n.use(initReactI18next).init({
|
|
75
|
+
resources,
|
|
76
|
+
lng: 'en',
|
|
77
|
+
interpolation: { escapeValue: false },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export default i18n;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Using translations in components
|
|
84
|
+
|
|
85
|
+
Import the translation hook and use it:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { useTranslation } from 'react-i18next';
|
|
89
|
+
import { useUserInterfaceLocale } from '@seriousgaming/sdk/ui';
|
|
90
|
+
|
|
91
|
+
function Dashboard() {
|
|
92
|
+
const { t } = useTranslation();
|
|
93
|
+
const { locale } = useUserInterfaceLocale();
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<h1>{t('greeting')}</h1>
|
|
98
|
+
<p>Current locale: {locale}</p>
|
|
99
|
+
<table>
|
|
100
|
+
<tr>
|
|
101
|
+
<th>{t('revenue')}</th>
|
|
102
|
+
<th>{t('sales')}</th>
|
|
103
|
+
<th>{t('stockLevel')}</th>
|
|
104
|
+
</tr>
|
|
105
|
+
</table>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `useUserInterfaceLocale()` hook provides the current locale from the dev server.
|
|
112
|
+
|
|
113
|
+
## Locale switching
|
|
114
|
+
|
|
115
|
+
In the dev server, change the session language with the locale select in the console's dev drawer (press the backtick key), which lists the locales the game supports. It sets the one session-wide locale. When it changes:
|
|
116
|
+
|
|
117
|
+
1. The dev server broadcasts the change to every open view and UI iframe
|
|
118
|
+
2. The SDK wrapper sets the document root's `lang` and `dir` (`rtl` for Arabic, Hebrew, Persian, Urdu and other right-to-left scripts)
|
|
119
|
+
3. i18next language changes
|
|
120
|
+
4. Components re-render with new translations
|
|
121
|
+
|
|
122
|
+
The choice holds for the rest of the session. No additional code is needed: just use `t()` in your components.
|
|
123
|
+
|
|
124
|
+
## Localising scenario content
|
|
125
|
+
|
|
126
|
+
The i18next setup above localises UI chrome: the buttons, labels, and other text that ships inside the user interface. Scenario-authored content is different. This is the prose a scenario carries but a UI renders: inbox documents, narrative emails, briefings, and transcripts. It needs its own approach.
|
|
127
|
+
|
|
128
|
+
### Locale is session-wide
|
|
129
|
+
|
|
130
|
+
A session runs in one locale, shared by every participant. The facilitator chooses it, the runtime broadcasts it to every connected surface, and it can change during a session. There is no per-participant locale, so two participants in the same session always see content in the same language.
|
|
131
|
+
|
|
132
|
+
### Resolve scenario content in the user interface
|
|
133
|
+
|
|
134
|
+
Keep the structural, locale-independent parts of your content (identifiers, ordering, triggers, and asset references) in context, where the engine needs them. Move the translatable prose into asset files under your scenario's `assets/` directory (or the package's `shared-assets/`), with each translation in a directory named for its locale beside the untranslated file:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
assets/
|
|
138
|
+
content/
|
|
139
|
+
documents.json ← the base language, and the fallback
|
|
140
|
+
ar/documents.json
|
|
141
|
+
it/documents.json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Read it with `useAsset`, which tries the surface's locale first (then its language, so `fr-CA` finds `fr/`) and falls back to the file as given:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { useAsset } from '@seriousgaming/sdk/ui';
|
|
148
|
+
|
|
149
|
+
type Documents = Record<string, { subject: string; body: string }>;
|
|
150
|
+
|
|
151
|
+
function useLocalisedDocuments() {
|
|
152
|
+
const { data } = useAsset<Documents>('asset:content/documents.json', { as: 'json' });
|
|
153
|
+
return data;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The fallback is per file: a locale with its own copy reads only that copy. A game that wants a partial translation to fall back entry by entry reads both, the localised copy with `locale` set and the base with `locale: false`, and merges them itself.
|
|
158
|
+
|
|
159
|
+
Because the UI already runs per participant and re-renders when the locale changes, content follows a mid-session locale switch for free.
|
|
160
|
+
|
|
161
|
+
> Do not resolve scenario content in an engine at `Init` and write it into a capability. That bakes one language into the engine context and every tick snapshot, and it cannot follow a mid-session locale change. Scenario content resolves in the UI.
|
|
162
|
+
|
|
163
|
+
### Generating localised prose in the engine
|
|
164
|
+
|
|
165
|
+
The UI pattern works for prose that exists ahead of time. For prose an engine generates at runtime, for example a narrative summary produced by `callbacks.generateText`, there is no pre-written translation to look up. Logic functions can read the session locale and generate in that language:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
export const Run: RunFunction<Context, Settings> = async ({ context, meta, callbacks }) => {
|
|
169
|
+
const summary = await callbacks.generateText(
|
|
170
|
+
`Write a one-paragraph board update in locale ${meta.locale}: ...`
|
|
171
|
+
);
|
|
172
|
+
return {
|
|
173
|
+
capabilities: {
|
|
174
|
+
...context,
|
|
175
|
+
board: { Update: summary.content },
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Every logic function reads the locale as `meta.locale`: the engine, aggregate functions, tick controllers, hooks, endpoints, and validators alike.
|
|
182
|
+
|
|
183
|
+
To test a locale headlessly, pass `--locale` when you initialise and tick a game:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
pnpm run sdk init --locale ar
|
|
187
|
+
pnpm run sdk tick --locale ar
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Organising translations
|
|
191
|
+
|
|
192
|
+
For larger UIs, organise translations by feature:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const resources = {
|
|
196
|
+
en: {
|
|
197
|
+
translation: {
|
|
198
|
+
"nav.dashboard": "Dashboard",
|
|
199
|
+
"nav.reports": "Reports",
|
|
200
|
+
"form.submit": "Submit",
|
|
201
|
+
"form.cancel": "Cancel",
|
|
202
|
+
"table.noData": "No data available"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
es: {
|
|
206
|
+
translation: {
|
|
207
|
+
"nav.dashboard": "Panel de Control",
|
|
208
|
+
"nav.reports": "Informes",
|
|
209
|
+
"form.submit": "Enviar",
|
|
210
|
+
"form.cancel": "Cancelar",
|
|
211
|
+
"table.noData": "No hay datos disponibles"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// In components
|
|
217
|
+
<button>{t('form.submit')}</button>
|
|
218
|
+
<span>{t('table.noData')}</span>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Formatting dates and numbers
|
|
222
|
+
|
|
223
|
+
Use i18next formatting for locale-specific formatting:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
function SalesReport({ revenue, date }) {
|
|
227
|
+
const { i18n } = useTranslation();
|
|
228
|
+
|
|
229
|
+
const formattedRevenue = new Intl.NumberFormat(i18n.language, {
|
|
230
|
+
style: 'currency',
|
|
231
|
+
currency: 'USD'
|
|
232
|
+
}).format(revenue);
|
|
233
|
+
|
|
234
|
+
const formattedDate = new Intl.DateTimeFormat(i18n.language).format(date);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div>
|
|
238
|
+
<p>Revenue: {formattedRevenue}</p>
|
|
239
|
+
<p>Date: {formattedDate}</p>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Best practices
|
|
246
|
+
|
|
247
|
+
1. **Complete translations** - Provide all languages for all strings
|
|
248
|
+
- ✅ Every English string has Spanish and French
|
|
249
|
+
- ❌ Missing translations in some languages
|
|
250
|
+
|
|
251
|
+
2. **Consistent naming** - Use dot notation for related strings
|
|
252
|
+
- ✅ `"form.submit"`, `"form.cancel"`, `"form.error"`
|
|
253
|
+
- ❌ `"submitButton"`, `"cancelBtn"`, `"errorMessage"`
|
|
254
|
+
|
|
255
|
+
3. **Use namespaces** - Organise large translation sets
|
|
256
|
+
- ✅ `"nav.home"`, `"dashboard.title"`, `"reports.nodata"`
|
|
257
|
+
- ❌ All strings in one flat object
|
|
258
|
+
|
|
259
|
+
4. **Keep UI in sync** - Update translations when UI changes
|
|
260
|
+
- ✅ Add new keys to all languages immediately
|
|
261
|
+
- ❌ Forget to translate new features
|
|
262
|
+
|
|
263
|
+
5. **Test all languages** - Switch locale in dev server to test
|
|
264
|
+
- ✅ Verify text displays correctly in each language
|
|
265
|
+
- ❌ Only test in English
|
|
266
|
+
|
|
267
|
+
## CLI examples
|
|
268
|
+
|
|
269
|
+
Make an English game multilingual, keeping every other manifest field:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
pnpm run sdk update package --patch '[
|
|
273
|
+
{"op": "replace", "path": "/supportedLocales", "value": ["en", "de", "ja"]},
|
|
274
|
+
{"op": "add", "path": "/locales/de", "value": {"name": "Geschäftsspiel"}},
|
|
275
|
+
{"op": "add", "path": "/locales/ja", "value": {"name": "ビジネスゲーム"}}
|
|
276
|
+
]'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Rename the game in one language:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
pnpm run sdk update package --patch '[{"op": "replace", "path": "/locales/en/name", "value": "Business Game"}]'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Next steps
|
|
286
|
+
|
|
287
|
+
- [Building user interfaces](./ui.md) - Build localised UIs
|
|
288
|
+
- [Terminology](../concepts/terminology.md) - The vocabulary these guides use
|