@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +93 -0
- package/README.md +38 -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 +27404 -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 +16920 -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 +218 -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 +241 -0
- package/dist/docs/cli-and-dev-server.md +217 -0
- package/dist/docs/cli-error-codes.md +156 -0
- package/dist/docs/cli-reference.md +2489 -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 +577 -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 +339 -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 +123 -3
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Validators
|
|
2
|
+
|
|
3
|
+
A validator is a read-only gate on what a participant does: a context write before it is applied, and a request for the next tick before it runs. It returns a verdict and never modifies the proposed state. The runtime applies the write, or runs the tick, only when every validator that runs allows it.
|
|
4
|
+
|
|
5
|
+
## The model
|
|
6
|
+
|
|
7
|
+
Some rules cannot be written in `variablesSchema`. JSON Schema handles the shape of the data ('Price must be a non-negative number'), but not a rule across fields, such as 'only three of these ten booleans may be true at once' or 'the budget across these fields must not exceed N'. The engine cannot gate such a rule either, because it runs on the tick rather than when a UI submits. That leaves the front end, which is the wrong layer for an integrity rule: devtools bypass it, and so do test scripts and future non-web clients.
|
|
8
|
+
|
|
9
|
+
A validator sits between a UI's submission and the runtime's apply step, on the server, so no client can get round it. Think of it as a verdict at the moment of submission, the way the engine is a tick-time computation and a [hook](./hooks.md) is a reaction to a runtime announcement:
|
|
10
|
+
|
|
11
|
+
- The runtime decides when validators run: on a player surface's context writes and tick requests, never inside the tick loop, and never on writes made by the game's own logic
|
|
12
|
+
- A validator subscribes to no signal. Given the previous state, the proposed next state, who is asking, and the settings it lists, it returns `{ ok: true }` or `{ ok: false, message }`
|
|
13
|
+
- A validator never modifies state. The runtime applies the proposed state untouched, and only when every validator allows it
|
|
14
|
+
- A validator runs when a write changes at least one capability in its `reads`, and one that declares no `reads` runs on every write. Every failure is collected and returned, so the experience is form-style rather than short-circuit
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
type ValidationResult = { ok: true } | { ok: false; message: string };
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A validator's logic exports one function per action it gates. `UpdateData` gates a context write; `RequestTick` gates a request for the next tick ([Gating tick requests](#gating-tick-requests)). Export either or both: a validator is run only for the actions it has a gate for, and the build reads which from the exports, so there is nothing to declare in the bundle.
|
|
21
|
+
|
|
22
|
+
### Why reject rather than modify
|
|
23
|
+
|
|
24
|
+
A 'clamp' verdict, where a validator rewrites the proposed state, is tempting. Validators do not offer one because:
|
|
25
|
+
|
|
26
|
+
- It conflates two shapes, gate and transform, which makes composing validators harder to reason about
|
|
27
|
+
- A clamp after the UI has submitted is a silent disagreement: the participant thinks they wrote X and the runtime stored Y. A clear rejection the UI can show is a better experience
|
|
28
|
+
- Anything that needs to transform state can do so in the engine on the next tick, or in the UI before it submits
|
|
29
|
+
|
|
30
|
+
### When to use a validator (versus an engine or a schema check)
|
|
31
|
+
|
|
32
|
+
- **`variablesSchema`**: an invariant on the shape of a single capability's data (numeric ranges, required fields, enum values). JSON Schema 7 expresses it natively
|
|
33
|
+
- **Engine**: the rule is part of normal tick-time evolution (clamping, derived state). The engine already changes context every tick
|
|
34
|
+
- **Hook**: the work reacts to a runtime-emitted lifecycle event (a participant joining, for example) after the runtime applies the change
|
|
35
|
+
- **Validator**: the rule is an integrity invariant that must hold whenever a UI submits a write, cannot fit inside `variablesSchema`, and needs to gate the write rather than react to it
|
|
36
|
+
|
|
37
|
+
Validators are also how a game arbitrates concurrent writes to a genuinely contested slot. When two participants can legitimately race for the same key (claiming a role, taking the last shared resource), no capability shape prevents the race, but a validator comparing `previous` against `next` can enforce 'first claim wins' or 'only write your own entry', so the losing write is refused loudly instead of silently overwriting. See [Design state for concurrent writes](../recipes/design-state-for-concurrent-writes.md).
|
|
38
|
+
|
|
39
|
+
## Creating a validator
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm run sdk new validator '{
|
|
43
|
+
"definition": {
|
|
44
|
+
"identifier": "decisionlimit",
|
|
45
|
+
"reads": ["decisions"]
|
|
46
|
+
},
|
|
47
|
+
"logic": {
|
|
48
|
+
"directory": "logic",
|
|
49
|
+
"entrypoint": "logic.ts"
|
|
50
|
+
},
|
|
51
|
+
"rootDirectory": "."
|
|
52
|
+
}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This produces:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
validators/decisionlimit/
|
|
59
|
+
├── validator.sdk.json # Definition (reads, settings)
|
|
60
|
+
├── logic/
|
|
61
|
+
│ ├── logic.ts # UpdateData, and optionally RequestTick
|
|
62
|
+
│ └── logic.test.ts
|
|
63
|
+
├── defaults.ts # Generated: the starting context and settings, for tests
|
|
64
|
+
└── types.ts # Generated: Context, Settings, ValidatorFunction, RequestTickFunction
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`identifier` names the directory; it is not written into `validator.sdk.json`. The capabilities in `reads` must already exist (here, a `decisions` capability).
|
|
68
|
+
|
|
69
|
+
### Wiring it to a game
|
|
70
|
+
|
|
71
|
+
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 validator` created is the whole of the wiring.
|
|
72
|
+
|
|
73
|
+
Validators run in directory-name order, and every failure is collected rather than short-circuiting, so number the directories if the order matters.
|
|
74
|
+
|
|
75
|
+
## Writing the logic
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import type { ValidatorFunction } from '../types';
|
|
79
|
+
|
|
80
|
+
export const UpdateData: ValidatorFunction = async ({ previous, next, actor, settings }) => {
|
|
81
|
+
const decisions = next.decisions;
|
|
82
|
+
const selected = Object.values(decisions).filter(Boolean).length;
|
|
83
|
+
if (selected > 3) {
|
|
84
|
+
return { ok: false, message: 'errors.max-three-decisions' };
|
|
85
|
+
}
|
|
86
|
+
return { ok: true };
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The validator receives:
|
|
91
|
+
|
|
92
|
+
- `previous`: the context before the mutation
|
|
93
|
+
- `next`: the proposed context after the mutation
|
|
94
|
+
- `actor`: who made the request (see [The actor](#the-actor))
|
|
95
|
+
- `settings`: the settings resources this validator lists in its bundle's `settings`, keyed by identifier
|
|
96
|
+
|
|
97
|
+
The `message` string is free-form. The SDK does not prescribe a format, so games encode whatever structure they need (a locale key, a JSON blob) inside it.
|
|
98
|
+
|
|
99
|
+
### Reads only
|
|
100
|
+
|
|
101
|
+
Validators declare `reads` but no `writes`. They are pure verdicts and cannot mutate state. The generated `Context` type reflects only the capabilities they read from.
|
|
102
|
+
|
|
103
|
+
## The actor
|
|
104
|
+
|
|
105
|
+
Each validator is told who submitted the mutation, as `actor`:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
type ValidatorActor =
|
|
109
|
+
{ role: 'participant'; participant: Participant } | { role: 'observer'; teamId: string };
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
It is a union rather than an optional participant, because these are two callers rather than an identity and the absence of one. `participant` is unreachable until you have narrowed on `role`, so a validator cannot read an identity it has not first decided it has.
|
|
113
|
+
|
|
114
|
+
A **participant** is playing. An **observer** is a facilitator watching one team on that team's own game UI, with that team's own controls, so the same rules hold for them unless you say otherwise:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
export const UpdateData: ValidatorFunction = async ({ next, actor, settings }) => {
|
|
118
|
+
// This game lets a facilitator past the cap. Deliberate, and worth being
|
|
119
|
+
// deliberate about: a rule the engine depends on should not be relaxed
|
|
120
|
+
// here, because that moves the breakage to the next tick.
|
|
121
|
+
if (actor.role === 'observer') return { ok: true };
|
|
122
|
+
...
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Whichever it is, `actor` always describes somebody real. A mutation the runtime cannot attribute is refused before any validator runs, so there is no placeholder to guard against (see [When the actor cannot be resolved](#when-the-actor-cannot-be-resolved)).
|
|
127
|
+
|
|
128
|
+
## Order of operations
|
|
129
|
+
|
|
130
|
+
For every UI `updateData` mutation, the runtime:
|
|
131
|
+
|
|
132
|
+
1. Resolves the actor: the participant the mutation is submitted on behalf of. This decides whether the rest of the list happens at all, and is covered below
|
|
133
|
+
2. Computes the proposed `next` state by applying the mutation to the current `previous` state, in memory only
|
|
134
|
+
3. Resolves the package's validators, in directory-name order
|
|
135
|
+
4. Runs each validator's `UpdateData` with `{ previous, next, actor, settings, meta, participants }`, and collects every failure. `participants` is the actor's team, and `meta.now` the instant the run started
|
|
136
|
+
5. If at least one validator returns `{ ok: false }`, it does **not** persist the patch, discards the proposed state, and returns the collected rejections (`{ validatorIdentifier, message }[]`) to the caller through the host ↔ iframe channel as a `validationRejected` port message
|
|
137
|
+
6. Otherwise, it applies and persists the patch as usual and broadcasts the resulting context
|
|
138
|
+
|
|
139
|
+
The UI decides how to render a rejection: a toast, an inline field error, or a modal.
|
|
140
|
+
|
|
141
|
+
### When the actor cannot be resolved
|
|
142
|
+
|
|
143
|
+
Step one has three outcomes, not two, and the third is the one worth knowing about.
|
|
144
|
+
|
|
145
|
+
- **A participant resolves.** The validators run, as above
|
|
146
|
+
- **There is nobody to resolve, and nobody was expected.** The mutation is system-trusted and skips validation entirely. This is the CLI, a session UI, and the runtime's own writes. An observer is _not_ in this list: it has no participant record, but the runtime knows exactly what it is, so its writes are validated and the validator is told
|
|
147
|
+
- **A participant was expected and none resolved.** The mutation is refused outright, as `denied`, and nothing is applied. No validator runs, because there is no actor to run one against
|
|
148
|
+
|
|
149
|
+
The third case is what a game UI produces when the runtime cannot bind it to anybody: a link followed after the session moved on, or a URL naming a participant who is not in the session. It is refused rather than skipped, because a runtime that cannot say who is writing cannot run an actor-aware check, and treating it as system-trusted would turn a failure to identify somebody into the widest trust the runtime has.
|
|
150
|
+
|
|
151
|
+
You will not normally see this. It matters because it must not be confused with the second case. The dev server holds the same line: a surface at `/team/1/player/3` on a team holding two participants is refused, rather than mounted and trusted.
|
|
152
|
+
|
|
153
|
+
## What bypasses validation
|
|
154
|
+
|
|
155
|
+
| Source | Bypasses validators? | Why |
|
|
156
|
+
| ---------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
157
|
+
| UI `updateData` | No, validators run | UI mutations originate from end-user actions; the rule must hold |
|
|
158
|
+
| UI `requestTick` | No, for a validator exporting `RequestTick` | A participant asking for the next tick; see [Gating tick requests](#gating-tick-requests) |
|
|
159
|
+
| Engines | Yes, system-trusted | Engines execute the game's own logic; validators would create infinite recursion |
|
|
160
|
+
| Hooks | Yes, system-trusted | Hooks react to lifecycle signals, not user input |
|
|
161
|
+
| CLI and tests | Yes, system-trusted | CLI mutations bypass when no participant is supplied (matches test usage) |
|
|
162
|
+
| Endpoints | Yes, system-trusted | An endpoint is the game's own code, and checks its own request before it writes |
|
|
163
|
+
|
|
164
|
+
### Validators and endpoints
|
|
165
|
+
|
|
166
|
+
An endpoint's write is not put to validators, whoever called it, including a participant's own game UI: a validator gates the one route where a participant changes state directly, and an endpoint is your code in the path. That lets a validator refuse a change outright while a strict endpoint becomes the one sanctioned way to make it. See [Endpoints and validators](./endpoints.md#endpoints-and-validators).
|
|
167
|
+
|
|
168
|
+
## Gating tick requests
|
|
169
|
+
|
|
170
|
+
A validator can also refuse a participant's request for the next tick. That is how a game enforces facilitator pacing: without it, the only thing holding a team at the round the facilitator has opened is a disabled button, and a participant's `requestTick` goes straight through.
|
|
171
|
+
|
|
172
|
+
Export `RequestTick` from the validator's logic. It receives the requesting team's board as it stands, as `context` (projected onto the validator's `reads`), and `requestedTick`, the tick the request would run: the team's current tick plus one, so a first request asks for tick 1. There is no `previous` and `next`, because a request changes nothing until the tick runs:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import type { RequestTickFunction } from '../types';
|
|
176
|
+
|
|
177
|
+
export const RequestTick: RequestTickFunction = async ({ requestedTick, context }) => {
|
|
178
|
+
const { LastOpenRound } = context.pacing;
|
|
179
|
+
if (requestedTick > LastOpenRound) {
|
|
180
|
+
return { ok: false, message: `Round ${requestedTick} is not open yet.` };
|
|
181
|
+
}
|
|
182
|
+
return { ok: true };
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
It runs on every request, before the tick starts. The rule that a validator runs only when a write changes its `reads` does not apply, because a request changes nothing.
|
|
187
|
+
|
|
188
|
+
A refusal stops the tick, and the participant's `requestTick()` rejects with the validator's message, as a refused write does. Only a request from a player surface (a participant's or an observer's) is gated. The facilitator's own tick, from the session surface or the console, and a timer's tick are never gated, which is what lets the facilitator run a round the room is not allowed to yet. Undo and reset are never validated. The `pacing` capability in the example above is a shared one the facilitator raises from the session UI as each round opens.
|
|
189
|
+
|
|
190
|
+
## Settings
|
|
191
|
+
|
|
192
|
+
A validator reads [settings](./settings.md) the way every other resource does: list the settings resources in the bundle's `settings`, and it receives exactly those as `settings`, keyed by identifier. They resolve through the same layers as the engine's, so a scenario or the facilitator's setup screen can change a limit without touching the logic.
|
|
193
|
+
|
|
194
|
+
A `decisionlimit` validator, for instance, can read its cap from `settings/decisionlimit/`:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"definition": {
|
|
199
|
+
"reads": ["addons"],
|
|
200
|
+
"settings": ["decisionlimit"]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
if (selected > settings.decisionlimit.MaxSelected) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
message: `At most ${settings.decisionlimit.MaxSelected} addons may be selected`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Related
|
|
215
|
+
|
|
216
|
+
- [Create a validator recipe](../recipes/create-validator.md): step-by-step scaffolding
|
|
217
|
+
- [Hooks guide](hooks.md): for out-of-tick reactions
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Building waiting room UIs
|
|
2
|
+
|
|
3
|
+
Own the holding screen participants see while a session is parked, instead of accepting the host's stock panel.
|
|
4
|
+
|
|
5
|
+
**Keywords:** waiting room, holding screen, lobby, waiting to begin, session paused, park a session, break, waitingRoom, setWaitingRoom
|
|
6
|
+
|
|
7
|
+
## What you'll learn
|
|
8
|
+
|
|
9
|
+
- What the waiting room is and who controls it
|
|
10
|
+
- Why a waiting room UI is a game UI rather than a new kind of surface
|
|
11
|
+
- How to tell 'not started yet' from 'parked mid-play'
|
|
12
|
+
- How to start every session with the room already open
|
|
13
|
+
- How to stop the tick clock while participants wait
|
|
14
|
+
- How to let your own session UI open and close the room
|
|
15
|
+
|
|
16
|
+
## What is the waiting room?
|
|
17
|
+
|
|
18
|
+
The waiting room is a single per-session flag. While it is open, every participant in the session sees a holding screen instead of the game UIs; while it is closed, they see the game. The facilitator toggles it, and the change reaches every team at once.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Waiting room closed Waiting room open
|
|
22
|
+
+---------------------+ +---------------------+
|
|
23
|
+
| | | |
|
|
24
|
+
| | | |
|
|
25
|
+
| Game UIs | | Waiting room UI |
|
|
26
|
+
| | | |
|
|
27
|
+
| | | |
|
|
28
|
+
+---------------------+ +---------------------+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Three properties are worth committing to memory, because each one surprises somebody:
|
|
32
|
+
|
|
33
|
+
| Property | What it means |
|
|
34
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| **Game state survives** | Per-team context, the current tick, and timers are untouched in both directions. Nothing is reset, which is exactly why facilitators use it to park a live session for a discussion |
|
|
36
|
+
| **The clock keeps running** | Automatic ticks still fire while the room is up. Halting play is a separate, opt-in decision (see [stopping the clock](#stopping-the-clock)) |
|
|
37
|
+
| **Joining still works** | Participants can join while the room is open and appear on the roster. `participantJoin` hooks fire as normal |
|
|
38
|
+
|
|
39
|
+
## A game UI in a different place
|
|
40
|
+
|
|
41
|
+
A waiting room UI is not a reduced surface. It has full parity with a game UI:
|
|
42
|
+
|
|
43
|
+
| Capability | Waiting room UI |
|
|
44
|
+
| ----------------------- | ---------------------- |
|
|
45
|
+
| `reads` | Yes |
|
|
46
|
+
| `writes` | Yes |
|
|
47
|
+
| `endpoints` | Yes |
|
|
48
|
+
| cross-team reads | Yes |
|
|
49
|
+
| `crossTeamParticipants` | Yes |
|
|
50
|
+
| Wrapper | `UserInterfaceWrapper` |
|
|
51
|
+
| Hooks | `useUserInterface*` |
|
|
52
|
+
|
|
53
|
+
There is deliberately no `WaitingRoomUserInterfaceWrapper` and no `useWaitingRoomUserInterfaceData`. A parallel API with identical behaviour would be two things to keep in step for no gain, so the room reuses the game UI's API exactly. Every recipe and guide about game UIs applies unchanged.
|
|
54
|
+
|
|
55
|
+
What the resource type buys you is the wiring, not a different runtime contract: its own directory, its own generated `types.ts`, a `waitingRoom` settings block on the game, and a mount rule that gives it the whole viewport.
|
|
56
|
+
|
|
57
|
+
## Creating one
|
|
58
|
+
|
|
59
|
+
The starter game already fills this slot, and `new` refuses a filled one with `RESOURCE_EXISTS`. In a package made by `new package`, change it with `update waiting_room` instead, or start from `new package --empty`.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pnpm run sdk new waiting_room '{
|
|
63
|
+
"definition": {
|
|
64
|
+
"reads": ["briefing"]
|
|
65
|
+
},
|
|
66
|
+
"rootDirectory": "."
|
|
67
|
+
}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
There is nothing to wire: a package declares whatever is on disk, and a game holds at most one waiting room, in the `waiting_room/` slot. To give the room values a game can vary, list the settings resources it reads in `settings`, as any surface does, and read them from `settings` (see [Settings](./settings.md#reading-settings)):
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"definition": {
|
|
75
|
+
"reads": ["briefing"],
|
|
76
|
+
"settings": ["waiting_room"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To hold participants from the moment they join rather than waiting on the facilitator, add `"openOnStart": true` (see [starting held](#starting-held)).
|
|
82
|
+
|
|
83
|
+
When a game has no `waiting_room/`, the host renders its own built-in holding screen. The SDK dev server does not reimplement that panel. Its **Waiting room** toggle is always in the dev drawer, and with the room open and none authored, a held participant sees a notice that the package has no waiting room UI rather than a holding screen.
|
|
84
|
+
|
|
85
|
+
## Reading game state
|
|
86
|
+
|
|
87
|
+
Same as any game UI. Declare the capability in `reads`, regenerate, and read it off `data`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { useUserInterfaceData } from '@seriousgaming/sdk/ui';
|
|
91
|
+
import type { Context, Settings } from '../types';
|
|
92
|
+
|
|
93
|
+
function App() {
|
|
94
|
+
const { data, settings, completedTicks } = useUserInterfaceData<Context, Settings>();
|
|
95
|
+
const briefing = data.briefing?.Headline ?? '';
|
|
96
|
+
// ...
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Because setup completes during session creation, and settings are fixed from then on, a waiting room UI is always mounted against an initialised context. `reads` return real values rather than placeholders, and `writes` always have somewhere to land.
|
|
101
|
+
|
|
102
|
+
## Not started vs parked mid-play
|
|
103
|
+
|
|
104
|
+
The room can be opened at any point in a session, so a single fixed message is wrong half the time. `completedTicks` tells the two situations apart:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const { completedTicks } = useUserInterfaceData<Context, Settings>();
|
|
108
|
+
|
|
109
|
+
return <h1>{completedTicks === 0 ? t('waitingToBegin') : t('sessionPaused')}</h1>;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is the most common reason authors reach for a custom room: the host's built-in panel says 'waiting to begin' and 'the facilitator will start the session shortly' whatever the tick, which reads as a bug to any participant parked halfway through a game.
|
|
113
|
+
|
|
114
|
+
## Starting held
|
|
115
|
+
|
|
116
|
+
By default a session starts with the room closed, so the first participant to follow a join link lands straight in the game at tick 0. For a game whose opening move belongs to the facilitator, that is a race: the room only holds anybody if the facilitator opens it before the join links go out.
|
|
117
|
+
|
|
118
|
+
`openOnStart` settles it in the manifest:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"waitingRoom": {
|
|
123
|
+
"openOnStart": true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Every session of that game now begins held, and participants see the room from the moment they join. Pair it with the `completedTicks === 0` branch above and the copy is right on both sides: 'waiting to begin' for the held start, 'session paused' for a mid-play park.
|
|
129
|
+
|
|
130
|
+
It seeds the session, it does not govern it. Three things follow:
|
|
131
|
+
|
|
132
|
+
- The facilitator owns the flag from the moment the session exists. Closing the room lets the teams in, and nothing reopens it behind their back
|
|
133
|
+
- A reset does not re-park the session. Reset keeps the roster and is not a new session, so the room stays exactly as the facilitator left it
|
|
134
|
+
- A dev server restart is a new session, so the flag is seeded from the manifest again. What the facilitator did to the last session does not carry over
|
|
135
|
+
|
|
136
|
+
A held start needs a room to hold people in, so `openOnStart` needs an authored `waiting_room/`. `sdk build` refuses `openOnStart: true` from the game or from any scenario (under `settings.game.waitingRoom`) when the package has none. It is one of the [game settings](./settings.md#the-game-settings), which a scenario can override but a setup screen cannot, because it takes effect as the session is created, before setup. A facilitator can still open the room mid-play in a game without one, which shows the host's built-in panel.
|
|
137
|
+
|
|
138
|
+
## Showing the roster
|
|
139
|
+
|
|
140
|
+
The game UI's participant hooks work here, which is what makes a useful holding screen possible:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import {
|
|
144
|
+
useUserInterfaceOwnTeam,
|
|
145
|
+
useUserInterfaceParticipant,
|
|
146
|
+
useUserInterfaceParticipants,
|
|
147
|
+
} from '@seriousgaming/sdk/ui';
|
|
148
|
+
|
|
149
|
+
const ownTeam = useUserInterfaceOwnTeam();
|
|
150
|
+
const participants = useUserInterfaceParticipants();
|
|
151
|
+
const self = useUserInterfaceParticipant();
|
|
152
|
+
|
|
153
|
+
const roster = [...participants].sort((a, b) => a.index - b.index);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Sort by `index`, not by name: the index is join order and is stable for the session. `participant.connected` is live, so a member closing their laptop dims in place. `self` is `null` when a facilitator is observing rather than playing, so guard before comparing.
|
|
157
|
+
|
|
158
|
+
## Showing a join code
|
|
159
|
+
|
|
160
|
+
The lobby is where a group sits while the facilitator sets up, so it is the natural place for a join code: a latecomer joins the team on the screen in front of them instead of waiting for someone to read a URL out.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { useUserInterfaceOwnTeamJoinLink } from '@seriousgaming/sdk/ui';
|
|
164
|
+
|
|
165
|
+
const joinLink = useUserInterfaceOwnTeamJoinLink();
|
|
166
|
+
// `null` until the host delivers it, so render nothing for that first frame
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
You get this team's link and only this team's. Encode `joinUrl` in the QR code rather than any longer form, and treat `fallbackJoinUrl` as a 'link not working?' affordance for networks that block the shortened one. The session PIN never reaches a game UI, so in a PIN-protected session the code alone does not admit anyone. See [Showing how to join this team](./ui.md) in the user interfaces guide for the full contract, which is identical here: a waiting room UI is a game UI in a different display context.
|
|
170
|
+
|
|
171
|
+
## Letting the group name itself
|
|
172
|
+
|
|
173
|
+
The lobby is where a group decides who it is, so it is the natural place for a name field. `renameOwnTeam(name)` sets the team's real name, the one the host's own chrome and print sheets show, so there is no second name to keep in sync:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { useUserInterfaceCallbacks } from '@seriousgaming/sdk/ui';
|
|
177
|
+
|
|
178
|
+
const { renameOwnTeam } = useUserInterfaceCallbacks();
|
|
179
|
+
await renameOwnTeam('The Cartographers');
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It names no team: the host resolves the caller's, so a surface can only rename its own. Render `useUserInterfaceOwnTeam()?.name` rather than what you sent, because a facilitator may rename the same team at the same moment and the last write wins. A blank or over-long name is refused as a terminal `MutationError`. See [Letting a team name itself](./ui.md) for the full contract.
|
|
183
|
+
|
|
184
|
+
## Stopping the clock
|
|
185
|
+
|
|
186
|
+
Opening the room pauses nothing. The decision belongs to the game, because only the author knows whether the simulation should keep advancing in the background:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import type { GetTickControllerFunction } from '@seriousgaming/sdk';
|
|
190
|
+
import type { Context, Settings } from '../types';
|
|
191
|
+
|
|
192
|
+
export const GetTickController: GetTickControllerFunction<Context, Settings> = async ({
|
|
193
|
+
waitingRoomOpen,
|
|
194
|
+
context,
|
|
195
|
+
}) => {
|
|
196
|
+
return {
|
|
197
|
+
enabled: !waitingRoomOpen,
|
|
198
|
+
intervalMs: 60_000,
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Without this, a facilitator who parks a session for a ten-minute discussion returns to teams whose game has moved on without them. With it, the countdown stops when the room opens and resumes when it closes.
|
|
204
|
+
|
|
205
|
+
## Layout
|
|
206
|
+
|
|
207
|
+
The room owns the viewport. It replaces the game UI. Two consequences:
|
|
208
|
+
|
|
209
|
+
- Any branding or status you want visible has to live inside the waiting room bundle
|
|
210
|
+
- Set `html, body, #root { margin: 0; height: 100% }` in `index.html` so a full-height layout actually fills the frame
|
|
211
|
+
|
|
212
|
+
## Opening the room from your own session UI
|
|
213
|
+
|
|
214
|
+
The host provides a toggle, but a session UI can drive the flag itself, which is useful when your game wants a 'start session' control of its own design:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { useSessionUserInterfaceCallbacks } from '@seriousgaming/sdk/ui';
|
|
218
|
+
|
|
219
|
+
const { setWaitingRoom } = useSessionUserInterfaceCallbacks();
|
|
220
|
+
|
|
221
|
+
await setWaitingRoom(true); // park the session
|
|
222
|
+
await setWaitingRoom(false); // resume
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Alone among the session controls it takes no `teamId`. The waiting room is one flag for the whole session, so there is no per-team variant to target. It returns a promise that resolves once the host confirms the change and rejects with a `MutationError` if it cannot apply it.
|
|
226
|
+
|
|
227
|
+
`setWaitingRoom` is not gated by `uses`: it sits with the other session flow controls (`pauseTimer`, `resumeTimer`, `resetGame`, `undoTick`) outside the `ApiUse` enum.
|
|
228
|
+
|
|
229
|
+
### Letting participants signal readiness
|
|
230
|
+
|
|
231
|
+
Nothing in a game UI bundle can close the room; that stays a facilitator action. If you want a 'we're ready' affordance, use the parity the room already has: write to a capability from the waiting room UI, and read it in your session UI so the facilitator can see who is waiting on whom.
|
|
232
|
+
|
|
233
|
+
## Testing
|
|
234
|
+
|
|
235
|
+
The room renders under the game UI's providers, so `renderUserInterface` tests it with no browser, taking `completedTicks` and `participants` as options: see [Game UIs](./testing.md#game-uis) in the testing guide. Vary `completedTicks` across two tests to cover both headings: that branch is the easiest thing to get wrong and the hardest to notice.
|
|
236
|
+
|
|
237
|
+
For the full loop, start the dev server, toggle the room, and watch the swap. See [end-to-end testing](../recipes/e2e-testing.md).
|
|
238
|
+
|
|
239
|
+
## Related
|
|
240
|
+
|
|
241
|
+
- [Create a waiting room UI](../recipes/create-waiting-room-ui.md): the step-by-step recipe
|
|
242
|
+
- [Building user interfaces](ui.md): the game UI and every hook the room shares with it
|
|
243
|
+
- [Tick controllers](tick-controller.md): where to stop the clock
|
|
244
|
+
- [Building session UIs](session.md): the session UI that can toggle the room
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Serious Gaming SDK documentation
|
|
2
|
+
|
|
3
|
+
The Serious Gaming SDK is for building business simulations: live, in-person learning interventions that a facilitator runs for a room of teams. You write a game as a package of TypeScript logic and React UIs, run it locally in the dev server, and publish it to the Serious Gaming platform.
|
|
4
|
+
|
|
5
|
+
## How a session runs
|
|
6
|
+
|
|
7
|
+
1. A facilitator starts a live session of your game and runs it from the session UI
|
|
8
|
+
2. Participants join on their own devices, in teams, and each sees your game UI
|
|
9
|
+
3. Teams make decisions: each change is a write to their team's state, checked by any validators you wrote
|
|
10
|
+
4. Each tick runs your engine on every team's state, and the game UIs show the result
|
|
11
|
+
5. After the last tick the game is complete, and the facilitator leads the debrief
|
|
12
|
+
|
|
13
|
+
## Start here
|
|
14
|
+
|
|
15
|
+
- **[Getting started](./getting-started.md)**: scaffold the starter game, play it, and change it
|
|
16
|
+
- **[A tour of the starter game](./guides/starter-game.md)**: every file in the game you are given, and what it does
|
|
17
|
+
- **[Terminology](./concepts/terminology.md)**: the words these docs use, each with one meaning
|
|
18
|
+
|
|
19
|
+
## The documentation
|
|
20
|
+
|
|
21
|
+
- **[Core concepts](./concepts/index.md)**: the model on one page, and how the resource kinds fit together
|
|
22
|
+
- **[Guides](./guides/index.md)**: one page per resource kind or topic, explaining it and serving as its reference
|
|
23
|
+
- **[Recipes](./recipes/index.md)**: one task each, step by step
|
|
24
|
+
- **[API reference](./api/index.md)**: every export of the SDK, the deck library and the test client, one page per entry point
|
|
25
|
+
|
|
26
|
+
## The CLI and the dev server
|
|
27
|
+
|
|
28
|
+
- **[The CLI and the dev server](./cli-and-dev-server.md)**: how the two share one session
|
|
29
|
+
- **[Drive a game from the CLI](./cli-simulation-testing.md)**: configure, tick, patch, and read a session from the terminal
|
|
30
|
+
- **[CLI reference](./cli-reference.md)**: every command and flag, including logging and output
|
|
31
|
+
- **[CLI error codes](./cli-error-codes.md)**: every code a failed command reports, and its exit code
|
|
32
|
+
- **[Environment variables](./environment-variables.md)**: every variable the dev server and CLI read
|
|
33
|
+
|
|
34
|
+
## When something changes or goes wrong
|
|
35
|
+
|
|
36
|
+
- **[Upgrading](./guides/upgrading.md)**: the steps from 0.6 to 1.0
|
|
37
|
+
- **[Changelog](./changelog.md)**: what changed in each release from 1.0, and what to do about it
|
|
38
|
+
- **[Changelog for 0.x](./changelog-0.x.md)**: every release before 1.0
|
|
39
|
+
- **[Troubleshooting](./troubleshooting.md)**: symptoms, causes, and fixes
|
|
40
|
+
|
|
41
|
+
The docs at sdk.seriousgaming.com describe the newest release. The docs for the version you have installed ship inside it: `pnpm run sdk docs view` lists them and prints any page, and `pnpm run sdk docs export` copies them into your package for an editor or agent to search.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Act on the session from an endpoint
|
|
2
|
+
|
|
3
|
+
**Keywords:** endpoint actions, advance tick from endpoint, close waiting room, start round, pause timer, extend tick, move participant, add team, facilitator macro, AI agent start round, Plan, Respond
|
|
4
|
+
|
|
5
|
+
Let one endpoint call do what a facilitator would otherwise do in several steps, such as posting a notice, closing the waiting room and starting the next round, under your game's own rules.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- An endpoint that already works (see [Create an endpoint](create-endpoint.md))
|
|
10
|
+
- The facilitator operations it should perform. Available: `tick`, `pauseTimer`, `resumeTimer`, `extendTick`, `setWaitingRoom`, `renameTeam`, `addTeam`, `moveParticipant` and `setLocale`
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. Declare the actions the endpoint may return:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"definition": {
|
|
19
|
+
"scope": "session",
|
|
20
|
+
"writes": ["noticeboard"],
|
|
21
|
+
"actions": ["setWaitingRoom", "tick"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Regenerate the types, which gives you an `Action` type holding just those:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm run sdk generate
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. Return the actions beside the response, deciding from `session`:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import type { Action, Handler } from '../types';
|
|
36
|
+
|
|
37
|
+
export const Handle: Handler = async ({ session, context }) => {
|
|
38
|
+
if (session.complete) throw new Error('The game is complete');
|
|
39
|
+
|
|
40
|
+
const actions: Action[] = [
|
|
41
|
+
...(session.waitingRoomOpen ? [{ type: 'setWaitingRoom' as const, open: false }] : []),
|
|
42
|
+
{ type: 'tick' },
|
|
43
|
+
];
|
|
44
|
+
return {
|
|
45
|
+
response: { fromRound: session.completedTicks },
|
|
46
|
+
capabilities: { noticeboard: { ...context.noticeboard, Message: 'Round starting' } },
|
|
47
|
+
actions,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The runtime applies the write, then the actions in order, in one step, and holds each action to the same rules as when the session UI performs it. A `teamId` left off an action means the call's own team on a team-scoped endpoint, and the whole session on a session-scoped one.
|
|
53
|
+
|
|
54
|
+
4. Decide who may call it. Actions are not put to your validators, so a participant whose screen lists this endpoint can trigger them. That is how a game lets a participant end their own turn. For a facilitator-only endpoint, refuse other callers:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const Handle: Handler = async ({ session, context, caller }) => {
|
|
58
|
+
if (caller.role === 'participant' || caller.role === 'observer') {
|
|
59
|
+
throw new Error('Only the facilitator can start a round');
|
|
60
|
+
}
|
|
61
|
+
// ...as in step 3
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
5. Optional: to report what the actions did, such as the round now in play, answer in two phases. Rename `Handle` to `Plan`, return only the effects from it, and export `Respond`, which runs after they have landed:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import type { Planner, Responder } from '../types';
|
|
69
|
+
|
|
70
|
+
export const Plan: Planner = async ({ session }) => ({ actions: [{ type: 'tick' }] });
|
|
71
|
+
|
|
72
|
+
export const Respond: Responder = async ({ session, outcomes }) => ({
|
|
73
|
+
round: session.completedTicks, // read after the tick
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Verify
|
|
78
|
+
|
|
79
|
+
- `pnpm run sdk endpoint call startround` (with `--external` for an external one) returns the response, and `pnpm run sdk view` shows the waiting room closed and the tick advanced
|
|
80
|
+
- `pnpm run sdk endpoint list` marks the endpoint `acts` and lists its actions
|
|
81
|
+
- Against a running dev server, `pnpm run sdk endpoint audit` shows the call and the actions it carried out
|
|
82
|
+
|
|
83
|
+
## Troubleshooting
|
|
84
|
+
|
|
85
|
+
- _'action 0 is "setWaitingRoom", which the endpoint does not declare'_: Add it to `actions`
|
|
86
|
+
- _'The game moved on while the endpoint was running'_: A tick landed while the handler ran, so its whole result was refused. Keep endpoints that act quick, and let the caller try again
|
|
87
|
+
- _'stopped at action 1 ("tick"): ... had already been applied'_: A step failed after earlier ones landed; they stay applied. Check the reason, such as the game being complete
|
|
88
|
+
- _'returned "renameTeam" with no teamId'_: A session-scoped endpoint has no team of its own; name one
|
|
89
|
+
- _'exports `Respond` beside `Handle`'_: Rename `Handle` to `Plan`
|
|
90
|
+
|
|
91
|
+
## Related
|
|
92
|
+
|
|
93
|
+
- [Endpoints (concept)](../guides/endpoints.md)
|
|
94
|
+
- [Make an endpoint callable from outside the game](make-an-endpoint-external.md)
|
|
95
|
+
- [Take a file in an endpoint](take-a-file-in-an-endpoint.md)
|