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