@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +93 -0
- package/README.md +36 -2
- package/dist/bundling/logic-exports.d.ts +34 -0
- package/dist/bundling/node-builtins.d.ts +21 -0
- package/dist/bundling/transpiler.d.ts +7 -0
- package/dist/cli.js +27368 -0
- package/dist/client/dev-server.d.ts +59 -0
- package/dist/client/index.d.ts +28 -0
- package/dist/client/stub-services.d.ts +32 -0
- package/dist/client/test-session.d.ts +195 -0
- package/dist/client.js +16884 -0
- package/dist/core/asset-reference-translator.d.ts +46 -0
- package/dist/core/clock.d.ts +57 -0
- package/dist/core/faults.d.ts +65 -0
- package/dist/core/game-settings.d.ts +65 -0
- package/dist/core/history.d.ts +89 -0
- package/dist/core/listen-with-fallback.d.ts +30 -0
- package/dist/core/mutation-queue.d.ts +61 -0
- package/dist/core/package-scenario.d.ts +89 -0
- package/dist/core/participant-registry.d.ts +109 -0
- package/dist/core/pending-changes.d.ts +14 -0
- package/dist/core/protocol.d.ts +83 -0
- package/dist/core/scenario-drafts.d.ts +32 -0
- package/dist/core/session-endpoints.d.ts +108 -0
- package/dist/core/session-gates.d.ts +75 -0
- package/dist/core/session-host.d.ts +94 -0
- package/dist/core/session-refusals.d.ts +56 -0
- package/dist/core/session-rewind.d.ts +32 -0
- package/dist/core/session-roster.d.ts +128 -0
- package/dist/core/session-surfaces.d.ts +32 -0
- package/dist/core/session-ticks.d.ts +18 -0
- package/dist/core/session-timers.d.ts +84 -0
- package/dist/core/session-writes.d.ts +40 -0
- package/dist/core/session.d.ts +385 -0
- package/dist/core/settings-locks.d.ts +22 -0
- package/dist/core/team-names.d.ts +52 -0
- package/dist/core/team-state-store.d.ts +209 -0
- package/dist/core/tick-timer.d.ts +75 -0
- package/dist/dev/blob-links.d.ts +10 -0
- package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
- package/dist/dev/console/index.html +12 -0
- package/dist/dev/console-path.d.ts +19 -0
- package/dist/dev/lockfile.d.ts +124 -0
- package/dist/dev/package-watcher.d.ts +21 -0
- package/dist/dev/routes.d.ts +67 -0
- package/dist/dev/shell.d.ts +260 -0
- package/dist/dev/surfaces.d.ts +41 -0
- package/dist/dev/tools-identity.d.ts +23 -0
- package/dist/dev/view/batch.d.ts +172 -0
- package/dist/dev/view/commands.d.ts +75 -0
- package/dist/dev/view/route.d.ts +58 -0
- package/dist/dev/view/view.d.ts +78 -0
- package/dist/dev/view/viewer.d.ts +32 -0
- package/dist/discovery/config.d.ts +9 -0
- package/dist/discovery/manifest-paths.d.ts +21 -0
- package/dist/discovery/removed-fields.d.ts +90 -0
- package/dist/docs/api/deck.md +386 -0
- package/dist/docs/api/index.md +67 -0
- package/dist/docs/api/sdk-commands.md +286 -0
- package/dist/docs/api/sdk-e2e.md +433 -0
- package/dist/docs/api/sdk-projection.md +207 -0
- package/dist/docs/api/sdk-testing.md +214 -0
- package/dist/docs/api/sdk-tools-client.md +560 -0
- package/dist/docs/api/sdk-ui-testing.md +231 -0
- package/dist/docs/api/sdk-ui.md +1269 -0
- package/dist/docs/api/sdk.md +2140 -0
- package/dist/docs/changelog-0.x.md +1156 -0
- package/dist/docs/changelog.md +236 -0
- package/dist/docs/cli-and-dev-server.md +217 -0
- package/dist/docs/cli-error-codes.md +154 -0
- package/dist/docs/cli-reference.md +2488 -0
- package/dist/docs/cli-simulation-testing.md +222 -0
- package/dist/docs/concepts/index.md +46 -0
- package/dist/docs/concepts/terminology.md +322 -0
- package/dist/docs/environment-variables.md +102 -0
- package/dist/docs/getting-started.md +145 -0
- package/dist/docs/guides/ai-facilitation.md +220 -0
- package/dist/docs/guides/capabilities.md +460 -0
- package/dist/docs/guides/endpoints.md +335 -0
- package/dist/docs/guides/engine-testing.md +477 -0
- package/dist/docs/guides/engine.md +527 -0
- package/dist/docs/guides/file-handling.md +405 -0
- package/dist/docs/guides/games.md +176 -0
- package/dist/docs/guides/global-settings.md +94 -0
- package/dist/docs/guides/guide.md +123 -0
- package/dist/docs/guides/hooks.md +183 -0
- package/dist/docs/guides/index.md +51 -0
- package/dist/docs/guides/internationalization.md +288 -0
- package/dist/docs/guides/llm-integration.md +635 -0
- package/dist/docs/guides/multi-team-games.md +586 -0
- package/dist/docs/guides/patterns.md +391 -0
- package/dist/docs/guides/presentation.md +196 -0
- package/dist/docs/guides/production-constraints.md +130 -0
- package/dist/docs/guides/scenario-editor.md +143 -0
- package/dist/docs/guides/scenarios.md +515 -0
- package/dist/docs/guides/session.md +972 -0
- package/dist/docs/guides/settings.md +458 -0
- package/dist/docs/guides/setup.md +193 -0
- package/dist/docs/guides/speech-generation.md +233 -0
- package/dist/docs/guides/starter-game.md +164 -0
- package/dist/docs/guides/testing.md +575 -0
- package/dist/docs/guides/tick-controller.md +569 -0
- package/dist/docs/guides/type-safety.md +516 -0
- package/dist/docs/guides/ui.md +1389 -0
- package/dist/docs/guides/upgrading.md +636 -0
- package/dist/docs/guides/validators.md +217 -0
- package/dist/docs/guides/waiting-room.md +244 -0
- package/dist/docs/index.md +41 -0
- package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
- package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
- package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
- package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
- package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
- package/dist/docs/recipes/create-capability.md +66 -0
- package/dist/docs/recipes/create-endpoint.md +127 -0
- package/dist/docs/recipes/create-engine.md +111 -0
- package/dist/docs/recipes/create-game.md +70 -0
- package/dist/docs/recipes/create-guide.md +75 -0
- package/dist/docs/recipes/create-hook.md +80 -0
- package/dist/docs/recipes/create-presentation.md +142 -0
- package/dist/docs/recipes/create-scenario-editor.md +102 -0
- package/dist/docs/recipes/create-scenario.md +98 -0
- package/dist/docs/recipes/create-session-ui.md +116 -0
- package/dist/docs/recipes/create-settings.md +88 -0
- package/dist/docs/recipes/create-setup.md +115 -0
- package/dist/docs/recipes/create-tick-controller.md +65 -0
- package/dist/docs/recipes/create-user-interface.md +96 -0
- package/dist/docs/recipes/create-validator.md +67 -0
- package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
- package/dist/docs/recipes/declare-api-uses.md +67 -0
- package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
- package/dist/docs/recipes/distribute-package.md +80 -0
- package/dist/docs/recipes/e2e-testing.md +116 -0
- package/dist/docs/recipes/end-game-on-condition.md +154 -0
- package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
- package/dist/docs/recipes/index.md +86 -0
- package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
- package/dist/docs/recipes/negotiate-across-teams.md +164 -0
- package/dist/docs/recipes/observe-other-teams-state.md +119 -0
- package/dist/docs/recipes/publish-a-package.md +139 -0
- package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
- package/dist/docs/recipes/review-a-past-day.md +73 -0
- package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
- package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
- package/dist/docs/recipes/show-all-teams-participants.md +109 -0
- package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
- package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
- package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
- package/dist/docs/recipes/test-user-interface.md +95 -0
- package/dist/docs/recipes/test-with-engine.md +115 -0
- package/dist/docs/recipes/upload-and-display-files.md +148 -0
- package/dist/docs/recipes/use-request-play.md +65 -0
- package/dist/docs/recipes/use-request-tick.md +75 -0
- package/dist/docs/troubleshooting.md +816 -0
- package/dist/execution/blob-inputs.d.ts +57 -0
- package/dist/execution/bundle-reload.d.ts +42 -0
- package/dist/execution/config-resolver.d.ts +127 -0
- package/dist/execution/configuration-builder.d.ts +66 -0
- package/dist/execution/context-composition.d.ts +161 -0
- package/dist/execution/endpoint-actions.d.ts +9 -0
- package/dist/execution/endpoint-payloads.d.ts +32 -0
- package/dist/execution/endpoint-runner.d.ts +129 -0
- package/dist/execution/engine-state.d.ts +40 -0
- package/dist/execution/executor-callbacks.d.ts +56 -0
- package/dist/execution/executor.d.ts +436 -0
- package/dist/execution/gates.d.ts +45 -0
- package/dist/execution/hook-chain.d.ts +25 -0
- package/dist/execution/logic-calls.d.ts +190 -0
- package/dist/execution/logic-failure.d.ts +84 -0
- package/dist/execution/logic-loader.d.ts +54 -0
- package/dist/execution/logic-result.d.ts +76 -0
- package/dist/execution/tick-controller-call.d.ts +32 -0
- package/dist/execution/types.d.ts +48 -0
- package/dist/generation/ts-type.d.ts +39 -0
- package/dist/guide/locale.d.ts +12 -0
- package/dist/guide/video-registry.d.ts +42 -0
- package/dist/help-examples.d.ts +125 -0
- package/dist/host/adapter.d.ts +67 -0
- package/dist/host/host-channel.d.ts +222 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/protocol.d.ts +783 -0
- package/dist/host/surfaces.d.ts +42 -0
- package/dist/host/theme.d.ts +55 -0
- package/dist/host.js +583 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/schema-builder.d.ts +29 -0
- package/dist/prompts/types.d.ts +108 -0
- package/dist/prompts/validators.d.ts +32 -0
- package/dist/runtime/capability-scope.d.ts +6 -0
- package/dist/runtime/context-merge.d.ts +29 -0
- package/dist/runtime/context-projection.d.ts +41 -0
- package/dist/runtime/guarded-callbacks.d.ts +15 -0
- package/dist/runtime/index.d.ts +27 -0
- package/dist/runtime/manifest-version.d.ts +55 -0
- package/dist/runtime/tick-validation.d.ts +25 -0
- package/dist/runtime.js +346 -0
- package/dist/schema.json +1546 -0
- package/dist/server/vite-plugin.d.ts +46 -0
- package/dist/services/tts/client.d.ts +51 -0
- package/dist/services/tts/elevenlabs.d.ts +24 -0
- package/dist/services/tts/types.d.ts +44 -0
- package/dist/storage/asset-resolver.d.ts +49 -0
- package/dist/storage/blob-store.d.ts +41 -0
- package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
- package/dist/templates/endpoint/logic.ts.liquid +25 -0
- package/dist/templates/engine/init.json.liquid +7 -0
- package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
- package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
- package/dist/templates/engine/logic.test.ts.liquid +61 -0
- package/dist/templates/engine/logic.ts.liquid +32 -0
- package/dist/templates/engine/run.json.liquid +7 -0
- package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
- package/dist/templates/guide/02-preparation.md.liquid +18 -0
- package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
- package/dist/templates/hook/logic.test.ts.liquid +46 -0
- package/dist/templates/hook/logic.ts.liquid +12 -0
- package/dist/templates/package/CLAUDE.md.template +338 -0
- package/dist/templates/package/DESIGN.md.liquid +144 -0
- package/dist/templates/package/README.md.liquid +143 -0
- package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
- package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
- package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
- package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
- package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
- package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
- package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
- package/dist/templates/package/claude/settings.json.template +16 -0
- package/dist/templates/package/gitignore.template +15 -0
- package/dist/templates/package/global-setup.ts.template +21 -0
- package/dist/templates/package/globals.d.ts.template +2 -0
- package/dist/templates/package/oxfmtrc.json.template +14 -0
- package/dist/templates/package/oxlintrc.json.template +238 -0
- package/dist/templates/package/package.json.liquid +65 -0
- package/dist/templates/package/playwright.config.ts.liquid +19 -0
- package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
- package/dist/templates/package/test-setup.ts.liquid +7 -0
- package/dist/templates/package/tsconfig.json.liquid +15 -0
- package/dist/templates/package/vite.config.ts.liquid +19 -0
- package/dist/templates/package/vitest.config.ts.liquid +25 -0
- package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
- package/dist/templates/presentation/index.html.liquid +20 -0
- package/dist/templates/presentation/src/App.tsx.liquid +49 -0
- package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
- package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
- package/dist/templates/presentation/src/main.tsx.liquid +14 -0
- package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
- package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
- package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
- package/dist/templates/session/src/App.test.tsx.liquid +60 -0
- package/dist/templates/session/src/App.tsx.liquid +128 -0
- package/dist/templates/session/src/i18n.ts.liquid +53 -0
- package/dist/templates/session/src/main.tsx.liquid +48 -0
- package/dist/templates/session/src/theme.ts.liquid +46 -0
- package/dist/templates/settings-editor/index.html.liquid +12 -0
- package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
- package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
- package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
- package/dist/templates/setup/src/App.tsx.liquid +102 -0
- package/dist/templates/setup/src/i18n.ts.liquid +47 -0
- package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
- package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
- package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
- package/dist/templates/starter-game/engine/defaults.ts +81 -0
- package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
- package/dist/templates/starter-game/engine/logic.test.ts +221 -0
- package/dist/templates/starter-game/engine/logic.ts +149 -0
- package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
- package/dist/templates/starter-game/engine/types.ts +139 -0
- package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
- package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
- package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
- package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
- package/dist/templates/starter-game/guide/guide.test.ts +108 -0
- package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
- package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
- package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
- package/dist/templates/starter-game/package.json +68 -0
- package/dist/templates/starter-game/playwright.config.ts +19 -0
- package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
- package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
- package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
- package/dist/templates/starter-game/session/defaults.ts +80 -0
- package/dist/templates/starter-game/session/index.html +12 -0
- package/dist/templates/starter-game/session/session.sdk.json +11 -0
- package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
- package/dist/templates/starter-game/session/src/App.tsx +309 -0
- package/dist/templates/starter-game/session/src/Section.tsx +37 -0
- package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
- package/dist/templates/starter-game/session/src/format.ts +8 -0
- package/dist/templates/starter-game/session/src/i18n.ts +51 -0
- package/dist/templates/starter-game/session/src/main.tsx +48 -0
- package/dist/templates/starter-game/session/src/theme.ts +45 -0
- package/dist/templates/starter-game/session/types.ts +135 -0
- package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
- package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
- package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
- package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
- package/dist/templates/starter-game/test-setup.ts +7 -0
- package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
- package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
- package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
- package/dist/templates/starter-game/tsconfig.json +19 -0
- package/dist/templates/starter-game/ui/defaults.ts +60 -0
- package/dist/templates/starter-game/ui/index.html +12 -0
- package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
- package/dist/templates/starter-game/ui/src/App.tsx +175 -0
- package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
- package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
- package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
- package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
- package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
- package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
- package/dist/templates/starter-game/ui/src/format.ts +12 -0
- package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
- package/dist/templates/starter-game/ui/src/main.tsx +48 -0
- package/dist/templates/starter-game/ui/src/menu.ts +18 -0
- package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
- package/dist/templates/starter-game/ui/src/theme.ts +45 -0
- package/dist/templates/starter-game/ui/src/writes.ts +43 -0
- package/dist/templates/starter-game/ui/types.ts +137 -0
- package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
- package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
- package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
- package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
- package/dist/templates/starter-game/vite.config.ts +19 -0
- package/dist/templates/starter-game/vitest.config.ts +15 -0
- package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
- package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
- package/dist/templates/starter-game/waiting_room/index.html +12 -0
- package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
- package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
- package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
- package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
- package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
- package/dist/templates/starter-game/waiting_room/types.ts +59 -0
- package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
- package/dist/templates/tick-controller/logic.ts.liquid +8 -0
- package/dist/templates/ui/index.html.liquid +12 -0
- package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
- package/dist/templates/ui/src/App.tsx.liquid +103 -0
- package/dist/templates/ui/src/i18n.ts.liquid +47 -0
- package/dist/templates/ui/src/main.tsx.liquid +48 -0
- package/dist/templates/ui/src/theme.ts.liquid +46 -0
- package/dist/templates/validator/logic.test.ts.liquid +42 -0
- package/dist/templates/validator/logic.ts.liquid +21 -0
- package/dist/templates/waiting-room/index.html.liquid +12 -0
- package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
- package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
- package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
- package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
- package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
- package/dist/templating/resource-metadata.d.ts +131 -0
- package/dist/types.d.ts +14 -0
- package/dist/utils/atomic-write.d.ts +40 -0
- package/dist/utils/cli-error.d.ts +64 -0
- package/dist/utils/document-parser-client.d.ts +20 -0
- package/dist/utils/env-loader.d.ts +40 -0
- package/dist/utils/error-codes.d.ts +335 -0
- package/dist/utils/errors.d.ts +19 -0
- package/dist/utils/global-settings.d.ts +65 -0
- package/dist/utils/llm-client.d.ts +34 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/output.d.ts +109 -0
- package/dist/utils/platform.d.ts +14 -0
- package/dist/utils/prompt.d.ts +36 -0
- package/dist/utils/real-path.d.ts +6 -0
- package/dist/utils/schema-properties.d.ts +35 -0
- package/dist/validation/ajv.d.ts +17 -0
- package/dist/validation/facilitator-guide-validator.d.ts +27 -0
- package/dist/validation/findings.d.ts +40 -0
- package/dist/validation/game-constraints.d.ts +10 -0
- package/dist/validation/game-validator.d.ts +34 -0
- package/dist/validation/package-validation.d.ts +41 -0
- package/dist/validation/package-validator.d.ts +19 -0
- package/dist/validation/resource-validator.d.ts +79 -0
- package/dist/validation/schema-defaults-validator.d.ts +29 -0
- package/dist/validation/schema-validation.d.ts +115 -0
- package/dist/validation/semver.d.ts +9 -0
- package/package.json +122 -3
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# Upgrading from 0.6 to 1.0
|
|
2
|
+
|
|
3
|
+
This guide takes a game package on 0.6 to 1.0, in 13 steps. Each step names what breaks and what to do about it. Step 1 renames the packages, so nothing compiles again until the end.
|
|
4
|
+
|
|
5
|
+
The steps are grouped by topic, not in the quickest order. Until the package loads, every CLI command reports what later steps have not fixed yet, so most games go fastest like this:
|
|
6
|
+
|
|
7
|
+
1. Make every edit that gets the package loading, in one pass: step 1, the bundle files and directories in step 2 (2a, 2b, 2c, 2e and 2g), step 3's moves, 7a and step 8
|
|
8
|
+
2. Run `pnpm run sdk generate`
|
|
9
|
+
3. Let `pnpm run typecheck` drive the rest: 2d and steps 4, 5, 6 and 10. One or two runs list everything
|
|
10
|
+
4. Search by hand for the changes no tool sees (below), then verify with step 13
|
|
11
|
+
|
|
12
|
+
Read your installed version first:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cat node_modules/@businesssimulations/seriousgaming-sdk/package.json | grep '"version"'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If it is earlier than 0.6.0, upgrade to 0.6 first with the upgrading guide that ships inside 0.6.0 (`node_modules/@businesssimulations/seriousgaming-sdk-tools/dist/docs/guides/upgrading.md` once 0.6 is installed). The history of every 0.x release is in the [0.x changelog](../changelog-0.x.md).
|
|
19
|
+
|
|
20
|
+
Most steps are mechanical and the compiler finds what they break. **These changes compile, build and pass unit tests while wrong**, so search for each by hand:
|
|
21
|
+
|
|
22
|
+
- Timer durations in milliseconds ([step 6c](#6c-timers-in-milliseconds))
|
|
23
|
+
- `caller.teamId` meaning the caller's own team ([step 7d](#7d-read-the-target-team-from-team))
|
|
24
|
+
- `null` no longer standing in for a removed entry ([step 4c](#4c-remove-entries-with-deletes-not-null))
|
|
25
|
+
- A session UI reading shared state through a team, behind a hand-written type or a cast ([step 5b](#5b-session-uis))
|
|
26
|
+
- Logic changing a capability outside its `writes`, and a per-team `Init` or `Run` writing a shared capability: both fail only on the call that changes the value ([step 9](#step-9-meet-what-the-build-and-runtime-now-enforce))
|
|
27
|
+
- A surface retrying a `conflict` on its own ([step 9](#step-9-meet-what-the-build-and-runtime-now-enforce))
|
|
28
|
+
- A test calling an endpoint with `uiIdentifier: ''`, which is now refused ([step 10a](#10a-move-off-gamerunner))
|
|
29
|
+
- Code that reads scenario files itself and still looks for `configuration` ([step 2c](#2c-rename-scenario-configuration-to-settings))
|
|
30
|
+
|
|
31
|
+
The [changelog](../changelog.md) carries the detail behind every step.
|
|
32
|
+
|
|
33
|
+
## Step 1: rename the packages
|
|
34
|
+
|
|
35
|
+
The three public packages move to the `@seriousgaming` scope on the public npm registry, and install without a token. Their subpaths are unchanged, and so is the `seriousgaming-sdk` command, so `pnpm run sdk` keeps working.
|
|
36
|
+
|
|
37
|
+
| Before | After |
|
|
38
|
+
| ---------------------------------------------- | -------------------------- |
|
|
39
|
+
| `@businesssimulations/seriousgaming-sdk-tools` | `@seriousgaming/sdk-tools` |
|
|
40
|
+
| `@businesssimulations/seriousgaming-sdk` | `@seriousgaming/sdk` |
|
|
41
|
+
| `@businesssimulations/seriousgaming-deck` | `@seriousgaming/deck` |
|
|
42
|
+
|
|
43
|
+
1. In `package.json`, rename the dependencies and set each to the 1.0 version. Keep the SDK, the tools and the deck at one version. `@seriousgaming/sdk-tools` declares `@seriousgaming/sdk` as a peer dependency at the same version
|
|
44
|
+
2. Replace the names everywhere else, in the order of the table so the SDK's name does not rewrite the start of the tools' name: every import (logic, surfaces, tests), `resolve.dedupe` in `vite.config.ts` and `vitest.config.ts`, `oxlintrc.json`, `testing/e2e/global-setup.ts` if you have one, `CLAUDE.md`, `.claude/rules/` and `README.md`. Then run your formatter: the new names sort differently, so every `package.json` and sorted import block changes
|
|
45
|
+
3. Remove the `@businesssimulations:registry=https://npm.pkg.github.com` line, and the token beside it, from `.npmrc` and from CI, unless something else you install still comes from there
|
|
46
|
+
4. In `pnpm-workspace.yaml`, delete the `sharp` and `protobufjs` overrides. They held back dependencies of `@xenova/transformers`, which left with `docs ask` (step 11), so nothing installs either package any more. Delete anything else you added for it too, such as `minimumReleaseAgeExclude` or `allowBuilds` entries for `@xenova/transformers` or `onnxruntime-node`
|
|
47
|
+
5. If you depend on `@seriousgaming/deck`, add `image-size: '>=2.0.3'` under `overrides:` (it clears an advisory reached through the deck's PowerPoint export). A game without the deck does not need it
|
|
48
|
+
6. Run `pnpm install`, then `pnpm update -r` so the dependencies your lockfile already holds reach their patched versions, then `pnpm audit`
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
grep -rn --exclude-dir=node_modules --exclude-dir=dist '@businesssimulations/seriousgaming-' .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The SDK and the deck are now Apache-2.0. The tools are under the Serious Gaming SDK Tools Licence in their `LICENSE` file; what they generate or copy into your project is yours under Apache-2.0.
|
|
55
|
+
|
|
56
|
+
## Step 2: move configuration into settings resources
|
|
57
|
+
|
|
58
|
+
A game's parameters now live in settings resources, `settings/<name>/settings.sdk.json`, rather than in a `configurationSchema` on each bundle. A resource lists the settings it reads in `settings`, as `reads` names capabilities, and receives exactly those, keyed by settings identifier. Any resource may read any settings resource. The game-level `configuration` block goes too: a game edits its own defaults. Settings are fixed when play starts. See [Settings](./settings.md) for the model.
|
|
59
|
+
|
|
60
|
+
### 2a. Move each `configurationSchema` into a settings resource
|
|
61
|
+
|
|
62
|
+
The build refuses a bundle that still carries one:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
`configurationSchema` has been removed. Move these properties into a settings resource (`pnpm run sdk new settings`), and list it in this resource's `settings`.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For each bundle that has one, create `settings/<slot>/settings.sdk.json` holding the same schema under `settingsSchema`, delete the `configurationSchema`, and list the new resource in the bundle's `settings`. Naming each after the slot it came from (`engine`, `ui`, `session`, `waiting_room`, `tick_controller`, or the hook's or validator's own name) keeps every scenario override's key the same.
|
|
69
|
+
|
|
70
|
+
Before, `engine/engine.sdk.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"definition": {
|
|
75
|
+
"reads": ["stall", "plan"],
|
|
76
|
+
"writes": ["stall", "plan"],
|
|
77
|
+
"configurationSchema": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"Capacity": { "type": "number", "default": 100, "minimum": 1 }
|
|
81
|
+
},
|
|
82
|
+
"required": ["Capacity"],
|
|
83
|
+
"additionalProperties": false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
After, `engine/engine.sdk.json` lists `"settings": ["engine"]` in place of the schema, and `settings/engine/settings.sdk.json` holds it:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"definition": {
|
|
94
|
+
"settingsSchema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"Capacity": { "type": "number", "default": 100, "minimum": 1 }
|
|
98
|
+
},
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Two rules are stricter than before. Every property needs a `default`, and a top-level `required` is refused, since every property always has a value. Delete the `required` array, and give any property without a default one (`"type": ["string", "null"]` with `"default": null` for a value that may be absent).
|
|
106
|
+
|
|
107
|
+
**An empty `configurationSchema` fails the build too.** A bundle carrying `"configurationSchema": { "type": "object", "properties": {} }` is refused with the same message, though there is nothing to move. Delete the block:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
grep -rln '"configurationSchema"' --include='*.sdk.json' .
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2b. Fold the game-level `configuration` block into the defaults
|
|
114
|
+
|
|
115
|
+
`seriousgaming.sdk.json` may no longer carry a `configuration` block, and the build refuses it. Move each value into the `default` of the matching settings property, then delete the block.
|
|
116
|
+
|
|
117
|
+
### 2c. Rename scenario `configuration` to `settings`
|
|
118
|
+
|
|
119
|
+
A scenario's overrides move from `definition.configuration` to `definition.settings`, still keyed by settings identifier, and the build refuses the old field. A configuration UI's `configures`, `sdk configure` overrides and `.seriousgaming-sdk/configuration.json` are keyed by settings identifier as well. If you named each settings resource after its slot in 2a, only the field name changes; otherwise rename each key to the settings resource that now holds the values.
|
|
120
|
+
|
|
121
|
+
The CLI can move the field, one scenario at a time:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pnpm run sdk update scenario short --patch '[{"op":"move","from":"/definition/configuration","path":"/definition/settings"}]'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Code that reads scenario files itself, such as a test that loads one or a script that generates them, still looks for `configuration` and keeps compiling, so a test can pass while checking nothing. Search for it:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
grep -rn "configuration" testing scripts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2d. Read `settings` rather than `config`
|
|
134
|
+
|
|
135
|
+
| Where | Before | After |
|
|
136
|
+
| ------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
|
137
|
+
| Engine, hooks, tick controller | `async ({ context, config }) => ...` | `async ({ context, settings }) => ...` |
|
|
138
|
+
| A read in logic | `config.Capacity` | `settings.engine.Capacity` |
|
|
139
|
+
| Validators | `({ previous, next, actor, config, meta })` | `({ previous, next, actor, settings, meta })` |
|
|
140
|
+
| Endpoints | `Handle({ request, context, ... })` | `Handle({ request, context, settings, ... })` |
|
|
141
|
+
| Type imports | `import type { Context, Config } from './types'` | `import type { Context, Settings } from './types'` |
|
|
142
|
+
| Function types | `RunFunction<Context, Config>`, `ModuleFunctionArgs` | `RunFunction<Context, Settings>`, `LogicFunctionArgs` |
|
|
143
|
+
| Game and waiting room UIs | `const { data, config } = useUserInterfaceData<Context, Config>()` | `const { data, settings } = useUserInterfaceData<Context, Settings>()` |
|
|
144
|
+
| Session UI | `useSessionUserInterfaceData<SessionData, Config>()` | `useSessionUserInterfaceData<SessionData, Settings>()` |
|
|
145
|
+
| Test fixtures | `import { defaultConfig } from './defaults'` | `import { defaultSettings } from './defaults'` |
|
|
146
|
+
| Component tests | `renderUserInterface(<App />, { config: { MaxPrice: 10 } })` | `renderUserInterface(<App />, { settings: { ui: { MaxPrice: 10 } } })` |
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import type { RunFunction } from '@seriousgaming/sdk';
|
|
150
|
+
import type { Context, Settings } from './types';
|
|
151
|
+
|
|
152
|
+
export const Run: RunFunction<Context, Settings> = async ({ context, settings }) => {
|
|
153
|
+
const capacity = settings.engine.Capacity;
|
|
154
|
+
// ...
|
|
155
|
+
return { capabilities: { ...context } };
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Settings arrive keyed by settings identifier (`settings.ui.MaxPrice`), where `config` was flat (`config.MaxPrice`). The generated types catch every typed read, but two kinds of code escape them:
|
|
160
|
+
|
|
161
|
+
- **Component tests that pass no settings.** A test that rendered with no `config` now throws on the first `settings.ui.X` read. Pass `defaultSettings`, or the keyed values the test needs
|
|
162
|
+
- **Untyped helpers and props.** A test harness or component that takes settings as a loose object, or a `config` prop passed by shorthand, keeps compiling with the old flat shape. Type each with the generated `Settings`
|
|
163
|
+
|
|
164
|
+
### 2e. Rename a capability called `settings`
|
|
165
|
+
|
|
166
|
+
A capability named `settings` now collides with the `settings` argument. Code that destructures both, such as `async ({ context, settings }) => { const { settings } = context; ... }`, or `const { data, settings } = useUserInterfaceData()` beside a read of `data.settings`, fails to compile or silently reads the wrong one. Such a capability usually holds configuration copied into context at `Init`: replace it with a settings resource (2f). If it holds state that changes during play, rename it, which moves its directory and rewrites every reference in the package's bundles, then rename the reads in your code:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm run sdk update capability settings --patch '[{"op":"add","path":"/definition/identifier","value":"game_state"}]'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2f. Delete the copies of configuration in context
|
|
173
|
+
|
|
174
|
+
Optional, and worth doing. A game that copied configuration into a capability at `Init`, so that a UI, hook or validator could read it, can list the settings resource on that reader instead, then delete the `Init` step, the capability and the reader's `reads` entry. Keep a capability where the value changes during play. Keep any value participants must not see, such as an answer key, in a settings resource only the engine declares.
|
|
175
|
+
|
|
176
|
+
### 2g. Rename `ticks` to `maxTicks`, and move a scenario's game fields into `settings.game`
|
|
177
|
+
|
|
178
|
+
The game-level settings (the number of ticks, the team bounds, the participant guidance, the default team names and whether the waiting room opens on start) are now a built-in settings resource called `game`. A game still declares its own values in `seriousgaming.sdk.json`, where they become that resource's defaults, so a scenario and a session's setup override them like any other setting. The build refuses the old fields, naming what replaced each.
|
|
179
|
+
|
|
180
|
+
In `seriousgaming.sdk.json`, rename `ticks` to `maxTicks`. `teams` and `waitingRoom` stay as they are, and `teams.min`, `teams.max` and the `perTeam` counts must now be whole numbers.
|
|
181
|
+
|
|
182
|
+
In each scenario, move `ticks`, `teams` and `waitingRoom` into `settings.game`, renaming `ticks` to `maxTicks`. Before:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"definition": {
|
|
187
|
+
"locales": { "en": { "name": "Short" } },
|
|
188
|
+
"ticks": 4,
|
|
189
|
+
"teams": { "max": 4 },
|
|
190
|
+
"settings": { "engine": { "BaseDemand": 120 } }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
After:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"definition": {
|
|
200
|
+
"locales": { "en": { "name": "Short" } },
|
|
201
|
+
"settings": {
|
|
202
|
+
"game": { "maxTicks": 4, "teams": { "max": 4 } },
|
|
203
|
+
"engine": { "BaseDemand": 120 }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Find what needs moving:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
grep -rln '"ticks"' --include='*.sdk.json' .
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Three things behave differently once you have:
|
|
216
|
+
|
|
217
|
+
- **A scenario's `teams.defaultNames` replaces the game's list whole.** It used to fall back to the game's name for a team past the end of the scenario's list. List every name the scenario needs
|
|
218
|
+
- **A presentation reads `game.maxTicks`**, where it read `game.ticks`
|
|
219
|
+
- **A settings resource called `game` is refused** (`SETTINGS_RESERVED`), since the name is the built-in one's. Rename yours, along with every `settings` and `configures` entry that names it
|
|
220
|
+
- **A single-team game locks `teams.max`.** `teams.max: 1` alone no longer makes a game single-team, because a scenario or setup could raise it. An independent game whose engine writes a shared capability fails the build until it adds `"gameSettingsConstraints": { "teams": { "max": { "readOnly": true } } }` beside `teams.max: 1` (see [Narrow or lock the game settings](./settings.md#narrow-or-lock-the-game-settings))
|
|
221
|
+
|
|
222
|
+
A setup UI or scenario editor can now set the number of rounds: list `game` in its `configures`, and write `updateSettings({ game: { ...settings.game, maxTicks: 6 } })`. A setup cannot set `waitingRoom.openOnStart`, which takes effect before setup runs. See [The game settings](./settings.md#the-game-settings).
|
|
223
|
+
|
|
224
|
+
## Step 3: move configuration UIs into the setup and scenario editor slots
|
|
225
|
+
|
|
226
|
+
A configuration UI used to be one resource wired into one or both surfaces through `configurationSurfaces`. Each surface is now its own slot, and the build refuses the old directory and key, naming the slots. See [Setup](./setup.md) and [The scenario editor](./scenario-editor.md).
|
|
227
|
+
|
|
228
|
+
| Was wired as | Move `configuration-user-interfaces/<name>/` to | Bundle file |
|
|
229
|
+
| ------------------ | ----------------------------------------------- | -------------------------- |
|
|
230
|
+
| `quick-config` | `setup/` | `setup.sdk.json` |
|
|
231
|
+
| `scenario-builder` | `scenario_editor/` | `scenario_editor.sdk.json` |
|
|
232
|
+
| Both | Both: copy it, then trim each copy to its job | Both |
|
|
233
|
+
|
|
234
|
+
Rename `configuration-user-interface.sdk.json` to the slot's bundle file, keep `configures` (keyed by settings identifier, step 2c), and delete `configurationSurfaces` from `seriousgaming.sdk.json`. `sdk new configuration-user-interface <name>` is now `sdk new setup` or `sdk new scenario_editor`, with no name. Then rename the code:
|
|
235
|
+
|
|
236
|
+
| Before | After |
|
|
237
|
+
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
238
|
+
| `ConfigurationUserInterfaceWrapper` | `SettingsEditorWrapper` |
|
|
239
|
+
| `useConfigurationUserInterfaceConfig()`, returning `{ config, setConfig }` | `useSettingsEditor()`, returning `{ settings, updateSettings, configures }` |
|
|
240
|
+
| `useConfigurationUserInterfaceTheme`, `…ThemePreference`, `…Locale`, `…Callbacks` | `useSettingsEditorTheme`, `…ThemePreference`, `…Locale`, `…Callbacks` |
|
|
241
|
+
| `ConfigurationUserInterfaceTheme`, `…ModeColors`, `…Locale`, `…Callbacks` | `SettingsEditorTheme`, `…ModeColors`, `…Locale`, `…Callbacks` |
|
|
242
|
+
| `useConfigurationUserInterfaceSurface`, `ConfigurationSurface` | Removed: each slot is its own bundle |
|
|
243
|
+
| `renderConfigurationUserInterface({ config, setConfig })`, `createMockConfigurationTheme` | `renderSettingsEditor({ settings, updateSettings, configures })`, `createMockSettingsEditorTheme` |
|
|
244
|
+
| Generated `Config`, `defaultConfig` | `Settings`, `defaultSettings` |
|
|
245
|
+
| `DEV_SERVER.configureUrl()`, `scenarioBuilderUrl()` | `setupUrl()`, `scenarioEditorUrl()` |
|
|
246
|
+
|
|
247
|
+
`renderSettingsEditor`'s `settings` is keyed by settings identifier, like everything else in step 2, and its `configures` defaults to the keys of `settings`. A UI that was wired into both surfaces has tests that render it for both: split them with the code.
|
|
248
|
+
|
|
249
|
+
The configuration step exists exactly when a game has `setup`. `callbacks.requestPlay` is always present there, and the host refuses it from the scenario editor. A game with custom scenarios should also set `"scenarioFormat": 1` in `seriousgaming.sdk.json` (see [Scenario format: when to bump it](./scenarios.md#scenario-format-when-to-bump-it)).
|
|
250
|
+
|
|
251
|
+
## Step 4: return what logic writes under `capabilities`
|
|
252
|
+
|
|
253
|
+
The engine, hooks and writing endpoints return what they write under a `capabilities` key, rather than as one flat object. The input `context` is unchanged. A result the runtime does not recognise fails the call, naming the key, so a function you miss fails on its first tick rather than losing its writes.
|
|
254
|
+
|
|
255
|
+
### 4a. Wrap each return
|
|
256
|
+
|
|
257
|
+
| Function | Before | After |
|
|
258
|
+
| --------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------- |
|
|
259
|
+
| Engine `Init` and `Run`, a hook's `Run` | `return { ...context, sales }` | `return { capabilities: { ...context, sales } }` |
|
|
260
|
+
| Engine `InitAggregate` / `RunAggregate` | `{ shared: { market }, teams: [ctx] }` | `{ shared: { capabilities: { market } }, teams: [{ capabilities: ctx }] }` |
|
|
261
|
+
| A writing endpoint's `Handle` | `{ response, context: { price } }` | `{ response, capabilities: { price } }` |
|
|
262
|
+
|
|
263
|
+
An early `return context;` in a hook becomes `return { capabilities: context };`. Validators, tick controllers and endpoints that declare no `writes` return what they did before. An engine with no `Init` now starts every team from its capabilities' defaults, so a no-op `Init` can be deleted.
|
|
264
|
+
|
|
265
|
+
### 4b. Retype engine steps
|
|
266
|
+
|
|
267
|
+
An engine that splits `Init` or `Run` into several functions typed as `RunFunction` will not compile, because that type now returns `{ capabilities }`. Type the steps as `StepFunction`, which still takes and returns the context flat, and wrap only in the exported function:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import type { RunFunction, StepFunction } from '@seriousgaming/sdk';
|
|
271
|
+
import type { Context, Settings } from './types';
|
|
272
|
+
|
|
273
|
+
export const runSales: StepFunction<Context, Settings> = async ({ context }) => ({
|
|
274
|
+
...context,
|
|
275
|
+
sales: { ...context.sales, Sales: 10 },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export const Run: RunFunction<Context, Settings> = async (args) => {
|
|
279
|
+
let context = args.context;
|
|
280
|
+
context = await runProduction({ ...args, context });
|
|
281
|
+
context = await runSales({ ...args, context });
|
|
282
|
+
return { capabilities: context };
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
An aggregate engine split into steps works the same way. There is no step type for it, so type each step over the team and shared contexts the generated `types.ts` exports, and wrap only in the exported function:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import type { RunAggregateFunction } from '@seriousgaming/sdk';
|
|
290
|
+
import type { Settings, SharedContext, TeamContext } from './types';
|
|
291
|
+
|
|
292
|
+
type Board = { shared: SharedContext; teams: TeamContext[] };
|
|
293
|
+
|
|
294
|
+
const settleMarket = (board: Board, settings: Settings): Board => ({
|
|
295
|
+
...board,
|
|
296
|
+
shared: { ...board.shared, market: { ...board.shared.market, Open: false } },
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export const RunAggregate: RunAggregateFunction<TeamContext, SharedContext, Settings> = async ({
|
|
300
|
+
shared,
|
|
301
|
+
teams,
|
|
302
|
+
settings,
|
|
303
|
+
}) => {
|
|
304
|
+
let board: Board = { shared: shared.context, teams: teams.map((team) => team.context) };
|
|
305
|
+
board = settleMarket(board, settings);
|
|
306
|
+
return {
|
|
307
|
+
shared: { capabilities: board.shared },
|
|
308
|
+
teams: board.teams.map((context) => ({ capabilities: context })),
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
A test that calls `Init`, `Run`, a hook or a writing endpoint directly reads one level deeper: `result.sales` becomes `result.capabilities.sales`, and an endpoint's `result.context` becomes `result.capabilities`. Tests through `createTestSession` read session state, so the result shape does not reach them, but its tick fields are renamed ([step 6a](#6a-tick-numbers)).
|
|
314
|
+
|
|
315
|
+
### 4c. Remove entries with `deletes`, not `null`
|
|
316
|
+
|
|
317
|
+
Writing `null` never removed anything, and in 1.0 there is a way that does. A UI's `setData` deep-merges its patch, so `setData({ offers: { Open: { [id]: null } } })` leaves the key in the map holding `null`, carried in every broadcast and every snapshot for the rest of the session. A logic result replaces each variable it returns whole, so a `null` there keeps the key too, or clears the whole variable. Code that wrote `null` as a tombstone, and filtered `null` entries out wherever it read the map, should name the entry in `deletes` instead:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// a UI
|
|
321
|
+
await setData({}, { deletes: [`offers.Open.${id}`] });
|
|
322
|
+
|
|
323
|
+
// the engine, a hook or a writing endpoint
|
|
324
|
+
return { capabilities: { ...context }, deletes: [`offers.Open.${id}`] };
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
A path is segments joined by `.`, or an array of segments when a key could contain a dot, and must sit under a capability in the writer's `writes`. Then drop the `null` filters, and `null` from the map's value schema if it was there only for tombstones. See [Removing a key](./engine.md#removing-a-key).
|
|
328
|
+
|
|
329
|
+
A session saved before the upgrade can still hold `null` entries, since nothing removes them. Keep a reader's `null` filter until no such session is left to resume, and only then drop it and the `null` from the schema. Update the capability's `description` too, and any comment beside the map, where it still says a removed entry is written as `null`. Nothing checks a description against the code, so a stale one stays wrong.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
grep -rnE ":\s*null\b" engine hooks endpoints ui session waiting_room --include='*.ts' --include='*.tsx'
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
That search finds a literal `null` only. A tombstone can also be computed (`open ? offer : null`, `reset[id] = null`) or written by a helper outside these directories, so search your capability schemas for maps whose values allow `null`, then read every writer of each.
|
|
336
|
+
|
|
337
|
+
Three more ways to remove an entry:
|
|
338
|
+
|
|
339
|
+
- **From a session UI.** Its `setData` takes the same option, for a team's capability or a shared one: `setData(teamId, {}, { deletes: [...] })` or `setData({}, { deletes: [...] })`. Each path must sit under a capability in the session UI's `writes`, in the scope it names
|
|
340
|
+
- **From logic, by leaving it out.** A logic result replaces each variable it returns whole, so returning the map without the key removes it too
|
|
341
|
+
- **From an undo stack.** A UI that keeps inverse patches to undo its own edits now records a `deletes` path as the inverse of an added key, and a write of the old value as the inverse of a removal
|
|
342
|
+
|
|
343
|
+
Returning `null` to clear a variable whose schema allows it still works: that is a value, not a removal.
|
|
344
|
+
|
|
345
|
+
## Step 5: read shared state from its own channel
|
|
346
|
+
|
|
347
|
+
The two places that see every team at once, an aggregate engine and a session UI, receive the shared store once, as `shared`, beside `teams`. Each team's context there carries that team's own capabilities only. `Init`, a per-team `Run`, hooks, team-scoped endpoints, game UIs and waiting rooms are unchanged.
|
|
348
|
+
|
|
349
|
+
### 5a. Aggregate engines
|
|
350
|
+
|
|
351
|
+
`InitAggregate` and `RunAggregate` take `{ shared, teams, settings, meta, callbacks, participants }`, and are typed with the team and shared contexts the generated `types.ts` exports:
|
|
352
|
+
|
|
353
|
+
| Before | After |
|
|
354
|
+
| ----------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
355
|
+
| `RunAggregateFunction<Context, Config>` | `RunAggregateFunction<TeamContext, SharedContext, Settings>` |
|
|
356
|
+
| `InitAggregateFunction<Context, Config>` | `InitAggregateFunction<TeamContext, SharedContext, Settings>` |
|
|
357
|
+
| `teams[0].context.market.AverageMarketPrice` | `shared.context.market.AverageMarketPrice` |
|
|
358
|
+
| `teams: teams.map((t) => ({ capabilities: { ...t.context, market } }))` | `shared: { capabilities: { market } }` |
|
|
359
|
+
|
|
360
|
+
**Drop every shared capability from the `teams` entries of the result.** One there now fails the init or tick, even when every team carries the same value; return it once, under `shared`. Spreading `...t.context` into a `teams` entry stays safe, because `t.context` no longer carries shared capabilities. In tests, pass `shared: { context: { ...defaultSharedContext } }` and build teams from `defaultTeamContext` (both from the generated `defaults.ts`), and read shared results from `result.shared.capabilities`.
|
|
361
|
+
|
|
362
|
+
### 5b. Session UIs
|
|
363
|
+
|
|
364
|
+
`SessionData` is `{ shared, teams }`: `sessionData.shared.context` holds the shared capabilities the UI declares, once, even with no teams, and each `sessionData.teams[teamId].context` holds that team's own. The generated `types.ts` exports `TeamContext`, `SharedContext`, `SharedWrites`, `TeamWrites` and a ready-made `SessionData`:
|
|
365
|
+
|
|
366
|
+
| Before | After |
|
|
367
|
+
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
368
|
+
| `type TypedSessionData = SessionData & { teams: Record<string, { context: Context }> }` | Delete it, and import `SessionData` from `../types` |
|
|
369
|
+
| `useSessionUserInterfaceData<TypedSessionData, Config, SharedContext>()` | `useSessionUserInterfaceData<SessionData, Settings, SharedWrites, TeamWrites>()` |
|
|
370
|
+
| `Object.values(sessionData.teams)[0]?.context.briefing.Message` | `sessionData.shared.context.briefing.Message` |
|
|
371
|
+
|
|
372
|
+
Move the third generic from `SharedContext` to `SharedWrites`: `SharedContext` now covers shared reads as well, so leaving it there compiles and lets `setData` accept a capability the UI only reads, which the host rejects. The fourth, `TeamWrites`, types the per-team `setData(teamId, update)`, so a team write naming a shared capability fails to compile too.
|
|
373
|
+
|
|
374
|
+
**A read of shared state through a team returns `undefined`, and only the generated types catch it.** The generated `SessionData` leaves shared capabilities off each team's context, so a read such as `Object.values(sessionData.teams)[0]?.context.briefing` fails to compile with it. It compiles with a hand-written `TypedSessionData`, with a cast such as `team.context as unknown as Context`, and with `useSessionUserInterfaceData()` called with no type arguments, whose contexts are untyped. Left alone, a facilitator sees blank views and gates that never open. Do this in order:
|
|
375
|
+
|
|
376
|
+
1. Delete every hand-written session data type and every cast of a team's context. Pass the generated `SessionData` to `useSessionUserInterfaceData`, and type every helper that takes session data or a team's context with `SessionData` or `TeamContext`
|
|
377
|
+
2. Run `pnpm run typecheck`, and move each read it reports to `sessionData.shared.context`
|
|
378
|
+
3. As a backstop, search `session/` for each shared capability's name: a read through a string key or a loosely typed helper still escapes the compiler
|
|
379
|
+
|
|
380
|
+
Watch for the reverse as well: 1.0 delivers shared state with no teams, so a view that waited for a team before it could read shared state now has it at once.
|
|
381
|
+
|
|
382
|
+
In component tests, put shared fixtures in `sessionData.shared`, built with `createMockSharedState`, and keep team fixtures to each team's own capabilities ([step 10d](#10d-update-ui-component-tests)).
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
grep -rn "TypedSessionData\|SessionData &\|as unknown as\|useSessionUserInterfaceData()\|useSessionUserInterfaceData<.*SharedContext" session
|
|
386
|
+
grep -rn "RunAggregate\|InitAggregate" engine
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Step 6: one name for each thing
|
|
390
|
+
|
|
391
|
+
The 1.0 vocabulary renames fields, messages and types so each concept has one name. Almost every rename fails to compile, so work through the errors with these tables beside you. 6c holds the changes the compiler cannot catch.
|
|
392
|
+
|
|
393
|
+
### 6a. Tick numbers
|
|
394
|
+
|
|
395
|
+
Two numbers shared one name. Nothing is renumbered.
|
|
396
|
+
|
|
397
|
+
| Before | After | Means |
|
|
398
|
+
| -------------------------------------------------------------------------------- | ---------------- | ---------------------------------------- |
|
|
399
|
+
| `meta.currentTick` (engine, aggregate engine, tick controller) | `meta.tick` | The tick being run: 1 on the first `Run` |
|
|
400
|
+
| `useUserInterfaceData().currentTick`, `renderUserInterface({ currentTick })` | `completedTicks` | Ticks completed: 0 before the first |
|
|
401
|
+
| A session UI team's `tick`, a presentation's `session.tick` | `completedTicks` | |
|
|
402
|
+
| An endpoint's `session.tick` and each team status's `tick` | `completedTicks` | |
|
|
403
|
+
| CLI JSON `currentTick` and per-team `tick` (`sdk view`, `sdk tick` and the rest) | `completedTicks` | |
|
|
404
|
+
| `createTestSession` state's session and per-team `tick` | `completedTicks` | |
|
|
405
|
+
|
|
406
|
+
`getPastState` snapshot numbers are unchanged: snapshot N is the state after tick N.
|
|
407
|
+
|
|
408
|
+
### 6b. Teams, participants and hooks
|
|
409
|
+
|
|
410
|
+
| Before | After |
|
|
411
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
412
|
+
| `teamName` on cross-team and past-state entries | `name`. Each entry also carries `index` |
|
|
413
|
+
| `SessionParticipant`, `UserInterfaceTeamParticipant`, `UserInterfaceParticipant` | `Participant` |
|
|
414
|
+
| `EndpointTeam` | `TeamInfo` |
|
|
415
|
+
| `EndpointCaller.role` as `string` | `EndpointCallerRole`: `'participant' \| 'observer' \| 'facilitator' \| 'author' \| 'external'` |
|
|
416
|
+
| A tick controller's optional `teams`, of bare contexts | `teams`, always present: `{ teamId, index, context }` per team, one in a single-team game |
|
|
417
|
+
| `useViewerRole`, `useOwnTeam`, `useOwnTeamJoinLink`, `useCrossTeamData` | `useUserInterfaceViewerRole`, `…OwnTeam`, `…OwnTeamJoinLink`, `…CrossTeamData` |
|
|
418
|
+
|
|
419
|
+
### 6c. Timers, in milliseconds
|
|
420
|
+
|
|
421
|
+
One `TimerState` type replaces `SurfaceTimer`, `UserInterfaceTimer` and the projection and endpoint timer types.
|
|
422
|
+
|
|
423
|
+
| Before | After |
|
|
424
|
+
| ------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
425
|
+
| `timerEnabled`, `isPaused`, `totalPausedMs` | `enabled`, `paused`, `pausedMs` |
|
|
426
|
+
| `interval` (seconds) | `intervalMs` (**milliseconds**) |
|
|
427
|
+
| Tick controller result `timerEnabled`, `interval` (seconds), `isComplete` | `enabled`, `intervalMs` (**milliseconds**), `complete` |
|
|
428
|
+
| `scheduledTick(at, { isComplete })` | `scheduledTick(at, { complete })` |
|
|
429
|
+
| `intervalTick(seconds)` | `intervalTick(intervalMs)` (**milliseconds**) |
|
|
430
|
+
| `extendTick(seconds, teamId?)` on a session UI | `extendTick(byMs, teamId?)` (**milliseconds**) |
|
|
431
|
+
| `MIN_EFFECTIVE_TICK_INTERVAL_SECONDS`, `MAX_…` | `MIN_EFFECTIVE_TICK_INTERVAL_MS`, `MAX_…` |
|
|
432
|
+
|
|
433
|
+
The unit changes compile unchanged, so search for each by hand:
|
|
434
|
+
|
|
435
|
+
- **`intervalTick(60)` now asks for 60 milliseconds** and is refused as below the 30-second minimum. Write `intervalTick(60_000)`
|
|
436
|
+
- **`extendTick(120)` now extends by 120 milliseconds.** Write `extendTick(120_000)`
|
|
437
|
+
- **A tick controller returning `interval: 60`** must return `intervalMs: 60_000`. `intervalMs` may be left out when the timer is off or you return `nextTickAt`
|
|
438
|
+
- **A settings property the build reads as a default interval** is now `intervalMs`, in milliseconds
|
|
439
|
+
- **`sdk timer --json`** reports a whole `TimerState`, so its interval is `intervalMs`
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
grep -rnE "intervalTick\(|extendTick\(|interval:|timerEnabled|isPaused" tick_controller session settings testing
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`sdk timer extend <seconds>` and `sdk clock advance <seconds>` still take seconds, because a person types them.
|
|
446
|
+
|
|
447
|
+
### 6d. Completion, record times, blob retention and the manifest types
|
|
448
|
+
|
|
449
|
+
| Before | After |
|
|
450
|
+
| --------------------------------------------------------------------------------------------- | ---------------------------- |
|
|
451
|
+
| `isComplete` on an endpoint's `session` and team statuses, and on past-state entries | `complete` |
|
|
452
|
+
| `timestamp` on past-state entries | `recordedAt` |
|
|
453
|
+
| `writeBlob(data, { retention: 'config' })` | `retention: 'settings'` |
|
|
454
|
+
| `sdk blob upload --config` | `sdk blob upload --settings` |
|
|
455
|
+
| `SDKConfiguration` (the authored `seriousgaming.sdk.json`) | `PackageManifest` |
|
|
456
|
+
| `Configuration` (the built `seriousgaming.json`) | `ResolvedPackageManifest` |
|
|
457
|
+
| `LogicConfiguration` | `LogicLayout` |
|
|
458
|
+
| `session.needsConfiguration` on session state (`createTestSession`, `sdk reconfigure --json`) | `session.inSetup` |
|
|
459
|
+
|
|
460
|
+
A `writeBlob` call still passing `'config'` is refused, naming the new value. A test fixture that builds past-state entries by hand needs the new names too:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
grep -rnE "isComplete|timestamp|retention: 'config'|SDKConfiguration|LogicConfiguration" engine endpoints hooks ui session testing
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Step 7: update endpoints
|
|
467
|
+
|
|
468
|
+
### 7a. Replace `agentCallable` with `visibility`
|
|
469
|
+
|
|
470
|
+
`visibility` says whether a caller outside the game may call an endpoint: a facilitator's AI agent, as before, or a platform API or other integration. The build refuses `agentCallable`. In each `endpoint.sdk.json`, replace `"agentCallable": true` with `"visibility": "external"`, and delete `"agentCallable": false`. `sdk endpoint list --agent` is now `--external`, and its JSON carries `visibility`. A caller outside the game is now refused an internal endpoint.
|
|
471
|
+
|
|
472
|
+
The build also refuses an external endpoint whose `requestSchema` is not `"type": "object"` at the top (`EXTERNAL_REQUEST_NOT_OBJECT`), since an outside caller passes named arguments. Wrap a bare value in an object, such as `{ "price": 3.53 }` for `3.53`, and read `request.price` in the handler. One that takes no input declares `{ "type": "object", "properties": {}, "additionalProperties": false }`.
|
|
473
|
+
|
|
474
|
+
### 7b. Handle the new arguments
|
|
475
|
+
|
|
476
|
+
Every handler receives `session` (where the session stands: `completedTicks`, `maxTicks`, whether the waiting room is open and the game complete, and each team's standing) and `settings`, and a team-scoped one receives `team: { teamId, index, name }`. The generated `Handler` type requires them, so only a unit test that builds the arguments by hand breaks; build them with `logicArgs.endpoint` (step 10b). A session-scoped handler's type has no `team`.
|
|
477
|
+
|
|
478
|
+
`caller.userInterfaceIdentifier` is optional, because a caller outside the game, and the CLI, has no surface. A handler that passed it on as a `string` needs a fallback, such as `caller.userInterfaceIdentifier ?? 'external'`, and one that checked for `'cli'` checks for the field being absent.
|
|
479
|
+
|
|
480
|
+
### 7c. Branch on an endpoint failure's kind
|
|
481
|
+
|
|
482
|
+
`callbacks.endpoints[id]()` rejects with an `EndpointError` rather than a plain `Error`. Code that matched on the message text branches on `error.kind` instead (a `MutationOutcome` kind, or `invalid-request`, `handler-error` or `invalid-response`), and can read `error.applied` to learn which effects landed before a call failed part-way. A failure part-way through reports the failed step's own kind (for example `tick-failed`), and a tick whose logic throws is `tick-failed` everywhere, where it used to be `error`. In tests, `createTestSession().send` answers a failed endpoint call with `{ type: 'endpointFailure', failure }` instead of throwing. See [When a call fails](./endpoints.md#when-a-call-fails).
|
|
483
|
+
|
|
484
|
+
### 7d. Read the target team from `team`
|
|
485
|
+
|
|
486
|
+
**`caller.teamId` changes meaning, and the compiler cannot see it.** It is now the caller's own team: present for a participant or an observer, absent for a facilitator or a caller outside the game. The team a team-scoped endpoint acts on is `team`:
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
grep -rn "caller.teamId" endpoints
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### 7e. Give the scenario editor endpoints that need no session
|
|
493
|
+
|
|
494
|
+
The scenario editor edits a scenario, not a session, so the build refuses one that lists an endpoint declaring any `reads`, `writes` or `actions` (`EDITOR_ENDPOINT_STATEFUL`). A 0.6 scenario builder could list one, because the dev server answered it from whatever session was running. For each endpoint the build names, either delete the `reads`, `writes` and `actions` it does not need, or give the editor an endpoint of its own without them. An endpoint the editor calls receives its declared `settings` as the scenario being edited resolves them, the editor's unsaved changes included, so declare `settings` rather than passing the values in the request. Its `context` is empty, `participants` is empty, `session` has played nothing and holds no teams, and `caller` carries `role: 'author'` rather than `'facilitator'` and no `sessionId`, which is now optional: a handler that used it as a `string` needs a fallback, and one that checked for `'facilitator'` to accept the editor's calls checks for `'author'`.
|
|
495
|
+
|
|
496
|
+
## Step 8: rename each validator's `Run` to `UpdateData`
|
|
497
|
+
|
|
498
|
+
A validator's logic exports one function per action it gates. Rename `Run` to `UpdateData`, and in tests call `UpdateData({ ... })`. The build refuses a validator that still exports `Run`, or neither gate:
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
grep -rln "export const Run" validators
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Optionally, if your game holds teams at a round the facilitator opens and enforces it only by disabling a button, export `RequestTick` too (see [Gating tick requests](./validators.md#gating-tick-requests)).
|
|
505
|
+
|
|
506
|
+
## Step 9: meet what the build and runtime now enforce
|
|
507
|
+
|
|
508
|
+
- **Delete `uses` from user interfaces.** A game UI, session UI, waiting room, setup or scenario editor that declares `uses` is refused by the build. It never had an effect: a surface reaches an LLM, text extraction or speech through an endpoint that declares the service
|
|
509
|
+
- **Delete demo mode.** `demo` in `seriousgaming.sdk.json` and in scenarios, the `GameDemo` type and `sdk dev --demo` are removed, and the build refuses the field. To show a game to someone, use a walkthrough run
|
|
510
|
+
- **Write only what you declare.** Logic that changes a capability outside its `writes` now fails the call, where 0.6 kept the change. That covers an engine's `Init` and `Run`, both halves of an aggregate result (`shared` and each `teams` entry), and hooks, whether the capability is one the resource only reads or one it never declared. Returning a capability unchanged, as `{ capabilities: { ...context } }` does, still passes. Add the capability to `writes`, or stop changing it. Code copied between games tends to carry the same undeclared write
|
|
511
|
+
- **Write shared capabilities only from the aggregate functions.** The build refuses a shared write declared by a hook, a surface or an engine without `InitAggregate` and `RunAggregate`. In an aggregate engine, a per-team `Init` or `Run` that writes a shared capability builds and passes unit tests, then stops the session starting or fails the tick. Move the write into `InitAggregate` or `RunAggregate`
|
|
512
|
+
- **Test every path that writes.** The two rules above are checked when the write happens, by comparing the value before and after, so a write that leaves the value as it was passes. An engine that changes a capability it only reads fails on the first tick that moves the value, which may be many ticks in. A per-team `Init` that writes shared state fails when a session starts. `sdk build` checks neither, and a one-tick `tick --no-server` passes every path that tick did not take. Have your engine tests play each round, each branch and each scenario that writes, not one tick (see [Writes are checked when they happen](./testing.md#writes-are-checked-when-they-happen))
|
|
513
|
+
- **Declare every capability a session UI changes in a team.** A session UI's team write that changes a capability outside its `writes`, or names a key that is not a capability, is refused as `denied`; 0.6 applied it. Sending a team's whole context back with one field edited still works, since a capability carried unchanged is not a change. The generated `TeamWrites` type catches most of these (step 5b); add the capability to the session UI's `writes`
|
|
514
|
+
- **Declare every capability a player surface writes.** A game UI or waiting room write (or `deletes` path) naming a capability outside its `writes`, or a key that is not a capability, is refused as `denied`. The generated types already forbid it, so only code that cast around them is affected: add the capability to the surface's `writes`
|
|
515
|
+
- **Keep Node out of logic.** Logic importing a Node built-in such as `fs`, `path` or `node:crypto` fails `sdk build`, `sdk dev` and `sdk tick` with `LOGIC_NODE_BUILTIN`, and the dev server no longer hands logic `require`. Production never had either. Use the callbacks for files, and `encodeUtf8` / `decodeUtf8` and `seededRandom` from the SDK for the rest
|
|
516
|
+
- **Drop the guards on declared services.** A service a resource declares in `uses` is always present on its generated `Callbacks`, so `callbacks.generateText?.(…)` and `callbacks.extractText!` become plain calls. `LogicCallbacks` takes `ApiUse` values as its type parameter
|
|
517
|
+
- **Never retry a conflict automatically.** A `conflict` that reaches a surface means the host already retried and gave up. Delete any automatic retry loop, show the failure, and offer `useMutationReporter`'s `failure.retry()` behind a 'Try again' button. The compiler cannot see a retry loop, so search your surfaces, and any UI library you share between games, for `'conflict'`
|
|
518
|
+
- **Imports that moved out of the SDK.** A game needs none of these, but if you imported them: the host library and its wire types moved from `@seriousgaming/sdk/host` and `./ui` to `@seriousgaming/sdk-tools/host`; the runtime helpers (`createGuardedCallbacks`, `deepMergeContext`, `projectContext`, `validateTickControllerResult`, `MANIFEST_VERSION` and others) moved from the SDK root to `@seriousgaming/sdk-tools/runtime`; `isApiUseDeclared`, `isDeliverableToSurfaces`, `withheldCapabilityIds`, `resolveManifestVersion`, `removedSharedFlagError` and `tickControllerResultSchema` are removed; and `ExecutionHistory`, `JsonPatchOperation`, `LLMGenerateOptions` and `GameContext` are no longer re-exported from `sdk-tools/client` (take `LLMGenerateOptions` from the SDK root). A test harness that merged a `setData` patch with `deepMergeContext` uses `mergingSetData(setLocal)` from `@seriousgaming/sdk/ui/testing`, which applies `deletes` as the host does
|
|
519
|
+
|
|
520
|
+
## Step 10: update the tests
|
|
521
|
+
|
|
522
|
+
### 10a. Move off `GameRunner`
|
|
523
|
+
|
|
524
|
+
`GameRunner` and `MultiTeamGameRunner`, deprecated in 0.6.0, are removed with their option and result types. Move each test to `createTestSession` with the table in [Moving from GameRunner](./engine-testing.md#moving-from-gamerunner):
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
grep -rln "GameRunner" testing
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
A test that calls an endpoint through `createTestSession` with `uiIdentifier: ''` to mean a system call is now refused as `denied`, and a test that expected a failure may still pass. Leave `uiIdentifier` out instead: a call that names no surface is system-trusted, as the CLI's is.
|
|
531
|
+
|
|
532
|
+
### 10b. Build logic arguments with `logicArgs`
|
|
533
|
+
|
|
534
|
+
Every logic call now receives `meta.now` (the operation's time in milliseconds) and a read-only `participants` array, an endpoint also `settings`, `session` and, team-scoped, `team`, and an aggregate `shared`. All are required by the argument types, so a test that builds arguments by hand stops compiling. Rather than adding each field, build them with `logicArgs` from `@seriousgaming/sdk/testing`, which fills `meta`, `participants`, the roster and offline stub callbacks around what the test names:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { logicArgs } from '@seriousgaming/sdk/testing';
|
|
538
|
+
import { defaultSettings } from '../../engine/defaults';
|
|
539
|
+
|
|
540
|
+
const result = await Run(logicArgs.run({ context, settings: defaultSettings }));
|
|
541
|
+
expect(result.capabilities.sales.Sales).toBe(10);
|
|
542
|
+
|
|
543
|
+
const answer = await Handle(logicArgs.endpoint({ request: { price: 3 }, context, settings: {} }));
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
There are builders for `init`, `run`, `aggregate`, `hook`, `endpoint`, `tickController`, `validator` and `requestTick`. See [Testing the Run function](./testing.md#testing-the-run-function).
|
|
547
|
+
|
|
548
|
+
**Replace `Date.now()` in logic with `meta.now`.** It is the same for every team in one tick, and a test controls it through the session clock.
|
|
549
|
+
|
|
550
|
+
Two builders need more than their names suggest:
|
|
551
|
+
|
|
552
|
+
- **`logicArgs.hook`** takes the signal's payload, so a `participantJoin` hook needs a whole `participant` (a `Participant`), not just an id
|
|
553
|
+
- **`logicArgs.endpoint`** builds a team-scoped call on `team-1` (index 0) from a participant caller. Pass `team`, `caller` or the session-scoped fields when a test needs others
|
|
554
|
+
|
|
555
|
+
### 10c. Opt in to real services where a test needs them
|
|
556
|
+
|
|
557
|
+
`createTestSession` no longer reaches the platform by default: `generateText`, `generateSpeech` and `extractText` return deterministic stubs, so a test runs without a network or a key. A test that relied on real responses passes `services: 'platform'`.
|
|
558
|
+
|
|
559
|
+
### 10d. Update UI component tests
|
|
560
|
+
|
|
561
|
+
The helpers in `@seriousgaming/sdk/ui/testing` build each piece; see [Testing](./testing.md):
|
|
562
|
+
|
|
563
|
+
- **`createMockTeamState` and `createMockSharedState`** build a team's and the shared store's state for a session UI test. Pass the generated types, `createMockTeamState<TeamContext>({ context })` and `createMockSharedState<SharedContext>({ context })`, and a shared capability left on a team fixture fails to compile. Render with `renderSessionUserInterface<SessionData>(...)` so the whole fixture is checked. Typing a fixture this way tends to turn up stale test data: fields the game renamed long ago, and tests that were passing while checking nothing. Treat each failure as a find, not a regression, and fix the test
|
|
564
|
+
- **`renderSettingsEditor`** replaces the provider tree a setup or scenario editor test built by hand (step 3)
|
|
565
|
+
- **`mergingSetData(setLocal)`** applies a `setData` call to local state the way the host does, `deletes` included, for a test that keeps state between calls
|
|
566
|
+
- **A hand-built session callbacks object needs `deleteTeam`**, which the session UI's callbacks now require. Build it with the testing helpers rather than by hand where you can
|
|
567
|
+
- **Theme palettes gained `contrastText`.** A hand-built theme object needs it on each colour, and code that loops over a palette's keys, in a test or in the UI, now meets it
|
|
568
|
+
|
|
569
|
+
## Step 11: update scripts and agents that drive the CLI
|
|
570
|
+
|
|
571
|
+
A game package does not change here, and a person at a terminal sees the same messages. Scripts, CI jobs and AI agents that parse the CLI's output do:
|
|
572
|
+
|
|
573
|
+
- **One envelope.** With `--json` (or `SERIOUSGAMING_JSON=1`) every command prints one document: `{ "ok": true, "command", "data", "diagnostics", "warnings" }` on success and `{ "ok": false, "command", "error": { "code", "message", "hint", "details" }, "diagnostics", "warnings" }` on failure. Read `ok` where you read `success`, `data.<field>` where you read a top-level field, and `error.code` where you matched on `error`. A failing `endpoint call` puts its failure in `error.details`
|
|
574
|
+
- **Exit codes by category.** Every failure used to exit 1. Now 1 is a bug in the CLI, 2 a usage error, 3 an invalid package, 4 a refusal by the game (a validator rejection, a failed tick) and 5 the environment (no session, a port in use, the platform unreachable). Replace `[ $? -eq 1 ]` with `[ $? -ne 0 ]`, or test for the category you mean, and branch on `error.code`, never on the message (see [Exit codes](../cli-reference.md#exit-codes))
|
|
575
|
+
- **Diagnostics say where.** A failure about the package lists its findings in `diagnostics`, each with a `code`, the `file`, and a `pointer` into it, or a `line` and `column` for a logic error. Read these rather than searching a message for a file name
|
|
576
|
+
- **`delete --force` is `delete --yes`.** Without a terminal the CLI never prompts: a command that would ask fails with exit 2 and `PROMPT_REQUIRED`. Pass definitions as an argument, with `--file` or on stdin, and confirmations with `--yes`
|
|
577
|
+
- **`new package` fails when its `pnpm install` fails** (exit 5, `INSTALL_FAILED`). The files are written; run `pnpm install` in the package
|
|
578
|
+
- **State readers.** The state projection that `sdk view` and `sdk play` print is version 4: `settings` in place of `configuration`, `completedTicks` in place of `tick`, `complete` in place of `isComplete`, a full `TimerState` and participants with `teamId`. `sdk tick --json` reports `complete`, and `sdk view --tick N` entries carry `complete` and `recordedAt` (were `isComplete` and `timestamp`)
|
|
579
|
+
- **`sdk blob upload --config` is `--settings`**, and its output's `retention` is `settings` where it was `config`
|
|
580
|
+
- **`sdk docs ask` and `sdk docs search` are removed.** Both now fail with `USAGE_REMOVED_COMMAND` (exit 2) and a pointer. Read a document with `pnpm run sdk docs view <path>`, search `node_modules/@seriousgaming/sdk-tools/dist/docs`, or copy the docs into the project with `pnpm run sdk docs export`. Agent guidance often names `docs ask` as its first instruction, so replace it in `CLAUDE.md` and `.claude/rules/` with that wording. Drop `HF_HOME` and `HUGGINGFACE_HUB_CACHE` if you set them only for `docs ask`
|
|
581
|
+
- **`runCommand({ type: 'updateConfig' })` is `updateSettings`**, with its payload keyed by settings identifier, for a walkthrough or script that drives the dev server
|
|
582
|
+
- **The dev server listens on `localhost` only.** To test on a phone or tablet, start it with `pnpm run sdk dev --lan`
|
|
583
|
+
- **A settings edit no longer applies mid-session.** Once a tick is played, `sdk tick`, `sdk undo`, a context patch and a writing endpoint call are refused with `SETTINGS_STALE` (exit 4) until you run `sdk init`; see [What reloads](../cli-reference.md#what-reloads)
|
|
584
|
+
- **`sdk team remove` refuses a team with participants**, and any team in a shared-state session. Move participants out with `sdk participant move` first
|
|
585
|
+
|
|
586
|
+
## Step 12: refresh the scaffold's furniture
|
|
587
|
+
|
|
588
|
+
Optional, but each item fixes a trap. A package created by `sdk new package` at 1.0 has all of them; an older one copies them from `node_modules/@seriousgaming/sdk-tools/dist/templates/package/`:
|
|
589
|
+
|
|
590
|
+
- **Playwright no longer hangs on teardown.** Copy `global-setup.ts.template` to `testing/e2e/global-setup.ts` and replace `playwright.config.ts`. The setup starts an isolated dev server with `startDevServer()` and sets `SERIOUSGAMING_DEV_URL`, which the `@seriousgaming/sdk/e2e` helpers read, so drop `{ port }` from their calls
|
|
591
|
+
- **A shared package can use the SDK's UI hooks.** Add `@seriousgaming/sdk` and `@seriousgaming/deck` to `resolve.dedupe` in `vite.config.ts` and `vitest.config.ts`, beside React and Emotion
|
|
592
|
+
- **The agent's pre-approved commands match its guidance.** Replace `.claude/settings.json`: it allows only read and check commands, never `dev`, `publish` or `pnpm add`
|
|
593
|
+
- **Lint fails on a warning.** Set the `lint` script to `oxlint --max-warnings=0`
|
|
594
|
+
- **The wrappers set `lang` and `dir`.** Delete the lines in each `main.tsx` that set them on the document
|
|
595
|
+
- **A shorter `CLAUDE.md` and refreshed rules.** Merge in the new `CLAUDE.md.template` and `claude/rules/` if you have not customised them heavily, as `workflow-update-sdk-version.md` describes
|
|
596
|
+
- **Refreshed dependencies.** Merge the versions in `package.json.liquid`, and after the Playwright bump run `pnpm exec playwright install chromium`
|
|
597
|
+
|
|
598
|
+
Nothing breaks if you skip the new features 1.0 adds, but several replace workarounds games carry: the engine's private `state` and `complete: true` (see [Engine](./engine.md#keeping-memory-between-ticks) and [Game completion](./tick-controller.md#game-completion)), capabilities with `"lifetime": "session"` that undo and reset leave alone ([Lifetime](./capabilities.md#lifetime)), a facilitator's slide deck in the `presentation/` slot ([Building presentations](./presentation.md)), endpoints that act on the session or take files ([Endpoints](./endpoints.md)), and the `useAsset`, `usePastState` and `useViewerStorage` hooks ([User interfaces](./ui.md)).
|
|
599
|
+
|
|
600
|
+
## Step 13: rebuild and verify
|
|
601
|
+
|
|
602
|
+
From the package root, in this order:
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
pnpm install
|
|
606
|
+
pnpm run sdk generate
|
|
607
|
+
pnpm run typecheck
|
|
608
|
+
pnpm run --silent sdk build --validate-only --json
|
|
609
|
+
pnpm run build
|
|
610
|
+
pnpm run lint
|
|
611
|
+
pnpm run test:run
|
|
612
|
+
pnpm run test:engine
|
|
613
|
+
pnpm run --silent sdk tick --no-server
|
|
614
|
+
pnpm run --silent sdk tick --no-server --scenario <id> # once for each scenario
|
|
615
|
+
pnpm run test:e2e
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
- `generate` rewrites every `types.ts` and `defaults.ts` from the new names, so the compiler can point at what is left
|
|
619
|
+
- `typecheck` lists every rename the tables above describe. Work through it until it is clean
|
|
620
|
+
- `build --validate-only --json` checks the package without bundling. On failure, read each entry of `diagnostics`: a `REMOVED_FIELD` names what replaced the field, and any other `SCHEMA_INVALID` carries its `file` and `pointer`
|
|
621
|
+
- `tick --no-server` starts a fresh session and plays one tick, for the default settings and each scenario. It is the only check that runs `Init` and `InitAggregate` against real settings, so it catches what builds and passes unit tests but stops a session starting. Do not skip it, but do not treat it as enough either: it plays one tick, and the write rules in step 9 fail only on a call that changes the value
|
|
622
|
+
- Read each command's exit code rather than its summary line: a test file that fails to load can leave a summary that counts only the tests that ran
|
|
623
|
+
- Then search by hand for the changes no tool sees, listed at the top of this guide
|
|
624
|
+
- Finally, play a round in the dev server and check each surface, the timer and completion
|
|
625
|
+
|
|
626
|
+
## Before you publish
|
|
627
|
+
|
|
628
|
+
A 1.0 build is a manifest version 3 package, and the platform must accept version 3 before you publish one. Until it does, the platform refuses the upload, and a version 2 package you published from 0.6 keeps running as it is. Check with Business Simulations that the platform you publish to accepts version 3 packages, and keep publishing from 0.6 until it does.
|
|
629
|
+
|
|
630
|
+
## Related
|
|
631
|
+
|
|
632
|
+
- [Changelog](../changelog.md): the detail behind every step
|
|
633
|
+
- [Changelog for 0.x](../changelog-0.x.md): every earlier release
|
|
634
|
+
- [Settings](./settings.md)
|
|
635
|
+
- [Engine testing](./engine-testing.md)
|
|
636
|
+
- [CLI reference](../cli-reference.md)
|