@seriousgaming/sdk-tools 1.0.0-alpha.1 → 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/README.md +2 -0
- package/dist/cli.js +40 -4
- package/dist/client.js +40 -4
- package/dist/core/session-endpoints.d.ts +1 -1
- package/dist/core/team-state-store.d.ts +9 -0
- package/dist/docs/changelog.md +5 -0
- package/dist/docs/cli-error-codes.md +3 -1
- package/dist/docs/cli-reference.md +1 -0
- package/dist/docs/guides/testing.md +2 -0
- package/dist/utils/error-codes.d.ts +5 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ The scaffolded package installs this package beside `@seriousgaming/sdk`, which
|
|
|
16
16
|
|
|
17
17
|
## Stability
|
|
18
18
|
|
|
19
|
+
A version with a prerelease suffix, such as `1.0.0-alpha.1`, carries none of these guarantees: it can change in any way before the release it leads to. Until 1.0.0 ships, npm's `latest` tag points at a prerelease, so a bare install gets one.
|
|
20
|
+
|
|
19
21
|
From 1.0.0, these follow semantic versioning. A breaking change waits for a major release:
|
|
20
22
|
|
|
21
23
|
| Surface | What it holds |
|
package/dist/cli.js
CHANGED
|
@@ -254,6 +254,10 @@ var ERROR_CODES = {
|
|
|
254
254
|
category: "package",
|
|
255
255
|
description: "A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it."
|
|
256
256
|
},
|
|
257
|
+
SCENARIO_EDITOR_UNREACHABLE: {
|
|
258
|
+
category: "package",
|
|
259
|
+
description: "A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it."
|
|
260
|
+
},
|
|
257
261
|
EDITOR_ENDPOINT_STATEFUL: {
|
|
258
262
|
category: "package",
|
|
259
263
|
description: "The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have."
|
|
@@ -6811,6 +6815,20 @@ function validateGame(game, config) {
|
|
|
6811
6815
|
hint: "Set `maxTicks` in seriousgaming.sdk.json, add a tick controller, or make sure Run returns complete: true."
|
|
6812
6816
|
});
|
|
6813
6817
|
}
|
|
6818
|
+
if (config.scenario_editor && game.definition.customScenarios?.supported !== true) {
|
|
6819
|
+
const editorId = config.scenario_editor.definition.identifier;
|
|
6820
|
+
const message = `Game "${game.definition.identifier}" ships a scenario_editor but does not set customScenarios.supported, so no host will ever open the editor.`;
|
|
6821
|
+
warnings.push(message);
|
|
6822
|
+
findings.push({
|
|
6823
|
+
severity: "warning",
|
|
6824
|
+
code: "SCENARIO_EDITOR_UNREACHABLE",
|
|
6825
|
+
message,
|
|
6826
|
+
file: MANIFEST_FILE,
|
|
6827
|
+
pointer: "",
|
|
6828
|
+
resource: { type: "scenario_editor", identifier: editorId },
|
|
6829
|
+
hint: 'Set `"customScenarios": { "supported": true }` in seriousgaming.sdk.json, or delete the scenario_editor if the game should not offer custom scenarios.'
|
|
6830
|
+
});
|
|
6831
|
+
}
|
|
6814
6832
|
const settingsReferences = collectUnknownSettingsReferences(config);
|
|
6815
6833
|
errors.push(...settingsReferences.errors);
|
|
6816
6834
|
findings.push(...settingsReferences.findings);
|
|
@@ -13562,14 +13580,15 @@ var SessionEndpoints = class {
|
|
|
13562
13580
|
session(roster2) {
|
|
13563
13581
|
const { executor, state: state2, participants } = this.host;
|
|
13564
13582
|
const ticks = state2.getTeamTicks();
|
|
13565
|
-
const
|
|
13583
|
+
const limit = this.host.tickLimit();
|
|
13584
|
+
const complete = state2.teamsFinished(limit);
|
|
13566
13585
|
const hasController = Boolean(executor.getConfig()?.tick_controller);
|
|
13567
13586
|
return {
|
|
13568
13587
|
mode: this.host.mode(),
|
|
13569
13588
|
completedTicks: state2.getCurrentTick(),
|
|
13570
13589
|
maxTicks: this.host.settings().maxTicks,
|
|
13571
13590
|
waitingRoomOpen: this.host.waitingRoomOpen(),
|
|
13572
|
-
complete: state2.
|
|
13591
|
+
complete: state2.isSessionFinished(limit),
|
|
13573
13592
|
teams: state2.getTeamIndexes().map((_, position) => {
|
|
13574
13593
|
const team = this.host.teamAt(position);
|
|
13575
13594
|
return {
|
|
@@ -15038,6 +15057,23 @@ var TeamStateStore = class {
|
|
|
15038
15057
|
isSessionComplete() {
|
|
15039
15058
|
return this.teamComplete.length > 0 && this.teamComplete.every(Boolean);
|
|
15040
15059
|
}
|
|
15060
|
+
/**
|
|
15061
|
+
* Whether each team can tick no more, which is what `complete` reports
|
|
15062
|
+
* (§9.0, §20.1): it signalled completion (§11.1) or played the tick limit
|
|
15063
|
+
* (§9.6). The stored flags stay the signal alone, because undo and past
|
|
15064
|
+
* state read the signal, not the limit.
|
|
15065
|
+
*/
|
|
15066
|
+
teamsFinished(limit) {
|
|
15067
|
+
return Array.from(
|
|
15068
|
+
{ length: this.teamCount },
|
|
15069
|
+
(_, position) => this.isTeamComplete(position) || limit !== null && (this.teamTicks[position] ?? this.currentTick) >= limit
|
|
15070
|
+
);
|
|
15071
|
+
}
|
|
15072
|
+
/** Whether no team can tick again: every team is finished. */
|
|
15073
|
+
isSessionFinished(limit) {
|
|
15074
|
+
const finished = this.teamsFinished(limit);
|
|
15075
|
+
return finished.length > 0 && finished.every(Boolean);
|
|
15076
|
+
}
|
|
15041
15077
|
// -- Stable index ↔ array position translation --------------------------
|
|
15042
15078
|
getTeamIndexes() {
|
|
15043
15079
|
return this.teamIndexes;
|
|
@@ -15540,7 +15576,7 @@ var Session = class {
|
|
|
15540
15576
|
const indexes = this.state.getTeamIndexes();
|
|
15541
15577
|
const contexts = this.state.getTeamContexts();
|
|
15542
15578
|
const ticks = this.state.getTeamTicks();
|
|
15543
|
-
const complete = this.state.
|
|
15579
|
+
const complete = this.state.teamsFinished(this.tickLimit());
|
|
15544
15580
|
const canUndo = this.state.teamCanUndo();
|
|
15545
15581
|
const game = this.game();
|
|
15546
15582
|
const { defaultNames } = this.settings().teams;
|
|
@@ -15576,7 +15612,7 @@ var Session = class {
|
|
|
15576
15612
|
completedTicks: this.state.getCurrentTick(),
|
|
15577
15613
|
mode: this.mode(),
|
|
15578
15614
|
waitingRoomOpen: this.waitingRoomOpen,
|
|
15579
|
-
complete: this.state.
|
|
15615
|
+
complete: this.state.isSessionFinished(this.tickLimit()),
|
|
15580
15616
|
seed: this.seed,
|
|
15581
15617
|
sessionKey: this.sessionKey,
|
|
15582
15618
|
locale: this.locale,
|
package/dist/client.js
CHANGED
|
@@ -473,6 +473,10 @@ var ERROR_CODES = {
|
|
|
473
473
|
category: "package",
|
|
474
474
|
description: "A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it."
|
|
475
475
|
},
|
|
476
|
+
SCENARIO_EDITOR_UNREACHABLE: {
|
|
477
|
+
category: "package",
|
|
478
|
+
description: "A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it."
|
|
479
|
+
},
|
|
476
480
|
EDITOR_ENDPOINT_STATEFUL: {
|
|
477
481
|
category: "package",
|
|
478
482
|
description: "The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have."
|
|
@@ -9655,14 +9659,15 @@ var SessionEndpoints = class {
|
|
|
9655
9659
|
session(roster) {
|
|
9656
9660
|
const { executor, state: state2, participants } = this.host;
|
|
9657
9661
|
const ticks = state2.getTeamTicks();
|
|
9658
|
-
const
|
|
9662
|
+
const limit = this.host.tickLimit();
|
|
9663
|
+
const complete = state2.teamsFinished(limit);
|
|
9659
9664
|
const hasController = Boolean(executor.getConfig()?.tick_controller);
|
|
9660
9665
|
return {
|
|
9661
9666
|
mode: this.host.mode(),
|
|
9662
9667
|
completedTicks: state2.getCurrentTick(),
|
|
9663
9668
|
maxTicks: this.host.settings().maxTicks,
|
|
9664
9669
|
waitingRoomOpen: this.host.waitingRoomOpen(),
|
|
9665
|
-
complete: state2.
|
|
9670
|
+
complete: state2.isSessionFinished(limit),
|
|
9666
9671
|
teams: state2.getTeamIndexes().map((_, position) => {
|
|
9667
9672
|
const team = this.host.teamAt(position);
|
|
9668
9673
|
return {
|
|
@@ -11174,6 +11179,23 @@ var TeamStateStore = class {
|
|
|
11174
11179
|
isSessionComplete() {
|
|
11175
11180
|
return this.teamComplete.length > 0 && this.teamComplete.every(Boolean);
|
|
11176
11181
|
}
|
|
11182
|
+
/**
|
|
11183
|
+
* Whether each team can tick no more, which is what `complete` reports
|
|
11184
|
+
* (§9.0, §20.1): it signalled completion (§11.1) or played the tick limit
|
|
11185
|
+
* (§9.6). The stored flags stay the signal alone, because undo and past
|
|
11186
|
+
* state read the signal, not the limit.
|
|
11187
|
+
*/
|
|
11188
|
+
teamsFinished(limit) {
|
|
11189
|
+
return Array.from(
|
|
11190
|
+
{ length: this.teamCount },
|
|
11191
|
+
(_, position) => this.isTeamComplete(position) || limit !== null && (this.teamTicks[position] ?? this.currentTick) >= limit
|
|
11192
|
+
);
|
|
11193
|
+
}
|
|
11194
|
+
/** Whether no team can tick again: every team is finished. */
|
|
11195
|
+
isSessionFinished(limit) {
|
|
11196
|
+
const finished = this.teamsFinished(limit);
|
|
11197
|
+
return finished.length > 0 && finished.every(Boolean);
|
|
11198
|
+
}
|
|
11177
11199
|
// -- Stable index ↔ array position translation --------------------------
|
|
11178
11200
|
getTeamIndexes() {
|
|
11179
11201
|
return this.teamIndexes;
|
|
@@ -11676,7 +11698,7 @@ var Session = class {
|
|
|
11676
11698
|
const indexes = this.state.getTeamIndexes();
|
|
11677
11699
|
const contexts = this.state.getTeamContexts();
|
|
11678
11700
|
const ticks = this.state.getTeamTicks();
|
|
11679
|
-
const complete = this.state.
|
|
11701
|
+
const complete = this.state.teamsFinished(this.tickLimit());
|
|
11680
11702
|
const canUndo = this.state.teamCanUndo();
|
|
11681
11703
|
const game = this.game();
|
|
11682
11704
|
const { defaultNames } = this.settings().teams;
|
|
@@ -11712,7 +11734,7 @@ var Session = class {
|
|
|
11712
11734
|
completedTicks: this.state.getCurrentTick(),
|
|
11713
11735
|
mode: this.mode(),
|
|
11714
11736
|
waitingRoomOpen: this.waitingRoomOpen,
|
|
11715
|
-
complete: this.state.
|
|
11737
|
+
complete: this.state.isSessionFinished(this.tickLimit()),
|
|
11716
11738
|
seed: this.seed,
|
|
11717
11739
|
sessionKey: this.sessionKey,
|
|
11718
11740
|
locale: this.locale,
|
|
@@ -13410,6 +13432,20 @@ function validateGame(game, config) {
|
|
|
13410
13432
|
hint: "Set `maxTicks` in seriousgaming.sdk.json, add a tick controller, or make sure Run returns complete: true."
|
|
13411
13433
|
});
|
|
13412
13434
|
}
|
|
13435
|
+
if (config.scenario_editor && game.definition.customScenarios?.supported !== true) {
|
|
13436
|
+
const editorId = config.scenario_editor.definition.identifier;
|
|
13437
|
+
const message = `Game "${game.definition.identifier}" ships a scenario_editor but does not set customScenarios.supported, so no host will ever open the editor.`;
|
|
13438
|
+
warnings.push(message);
|
|
13439
|
+
findings.push({
|
|
13440
|
+
severity: "warning",
|
|
13441
|
+
code: "SCENARIO_EDITOR_UNREACHABLE",
|
|
13442
|
+
message,
|
|
13443
|
+
file: MANIFEST_FILE,
|
|
13444
|
+
pointer: "",
|
|
13445
|
+
resource: { type: "scenario_editor", identifier: editorId },
|
|
13446
|
+
hint: 'Set `"customScenarios": { "supported": true }` in seriousgaming.sdk.json, or delete the scenario_editor if the game should not offer custom scenarios.'
|
|
13447
|
+
});
|
|
13448
|
+
}
|
|
13413
13449
|
const settingsReferences = collectUnknownSettingsReferences(config);
|
|
13414
13450
|
errors.push(...settingsReferences.errors);
|
|
13415
13451
|
findings.push(...settingsReferences.findings);
|
|
@@ -17,7 +17,7 @@ import type { SessionHost } from './session-host';
|
|
|
17
17
|
type CallEndpoint = Extract<QueryCommand, {
|
|
18
18
|
type: 'callEndpoint';
|
|
19
19
|
}>;
|
|
20
|
-
type EndpointHost = Pick<SessionHost, 'executor' | 'state' | 'participants' | 'faults' | 'clock' | 'timers' | 'pendingChanges' | 'mode' | 'settings' | 'scenarioBeingEdited' | 'waitingRoomOpen' | 'positionOf' | 'indexOfTeamId' | 'teamAt' | 'roster' | 'declaredSurface' | 'enqueue' | 'apply'>;
|
|
20
|
+
type EndpointHost = Pick<SessionHost, 'executor' | 'state' | 'participants' | 'faults' | 'clock' | 'timers' | 'pendingChanges' | 'mode' | 'settings' | 'tickLimit' | 'scenarioBeingEdited' | 'waitingRoomOpen' | 'positionOf' | 'indexOfTeamId' | 'teamAt' | 'roster' | 'declaredSurface' | 'enqueue' | 'apply'>;
|
|
21
21
|
export declare class SessionEndpoints {
|
|
22
22
|
private readonly host;
|
|
23
23
|
private readonly audit;
|
|
@@ -118,6 +118,15 @@ export declare class TeamStateStore {
|
|
|
118
118
|
* others still tick.
|
|
119
119
|
*/
|
|
120
120
|
isSessionComplete(): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Whether each team can tick no more, which is what `complete` reports
|
|
123
|
+
* (§9.0, §20.1): it signalled completion (§11.1) or played the tick limit
|
|
124
|
+
* (§9.6). The stored flags stay the signal alone, because undo and past
|
|
125
|
+
* state read the signal, not the limit.
|
|
126
|
+
*/
|
|
127
|
+
teamsFinished(limit: number | null): boolean[];
|
|
128
|
+
/** Whether no team can tick again: every team is finished. */
|
|
129
|
+
isSessionFinished(limit: number | null): boolean;
|
|
121
130
|
getTeamIndexes(): number[];
|
|
122
131
|
setTeamIndexes(indexes: number[]): void;
|
|
123
132
|
/** Stable index for the team at array position `position`. */
|
package/dist/docs/changelog.md
CHANGED
|
@@ -141,6 +141,7 @@ are in the [0.x changelog](changelog-0.x.md).
|
|
|
141
141
|
- **`SERIOUSGAMING_DEV_STATE_DIR`** keeps the dev server's lock file outside the package
|
|
142
142
|
- **For runtimes: the manifest schema and the logic-result checks.** `@seriousgaming/sdk-tools/schema.json` is the JSON Schema of this release's manifest version, its `ResolvedPackageManifest` the built `seriousgaming.json`, and `@seriousgaming/sdk-tools/runtime` adds `assertJsonSerialisable`, `readEngineResult`, `readAggregateResult` and `readTickControllerResult`. A game needs none of them
|
|
143
143
|
- **The SDK has a README** saying which subpaths carry semver guarantees (the root, `./ui` and `./ui/testing`)
|
|
144
|
+
- **`SCENARIO_EDITOR_UNREACHABLE`, a build warning** for a game that ships a `scenario_editor` without `customScenarios.supported`, which no host ever opens. Set the flag, or delete the editor
|
|
144
145
|
|
|
145
146
|
### Changed
|
|
146
147
|
|
|
@@ -178,6 +179,10 @@ are in the [0.x changelog](changelog-0.x.md).
|
|
|
178
179
|
- **The conformance suite reports more of a production host fairly.** Checks that call an endpoint from outside the game require the new `external-callers` driver capability and checks that hold a session at setup require `setup-state`, so a host without either reports them unsupported. REQ-014 and REQ-020 check the outcome rather than a message or the host page's own traffic, each built fixture variant has its own package identifier, a new check holds a participant's `getPastState` to its own surface's rights whatever surface it claims, and the moved-participant check waits for the remount instead of racing it
|
|
179
180
|
- **Three doc pages have new names**: `guides/game-configuration.md` is [`guides/settings.md`](guides/settings.md), `guides/examples.md` is [`guides/patterns.md`](guides/patterns.md), and `recipes/share-logic-between-module-and-ui.md` is [`recipes/share-logic-between-engine-and-ui.md`](recipes/share-logic-between-engine-and-ui.md). The docs no longer point at example packages you cannot see, the publishing and LLM guides say an account needs access Business Simulations grants (as the 403 messages from `package publish` and `generateText` now do, and the scaffold's `workflow-publish-package.md`), and `llms.txt` and `llms-full.txt` on the docs site link by absolute URL
|
|
180
181
|
|
|
182
|
+
- **`complete` counts the tick limit.** On an endpoint's `session`, its team statuses and the state projection `sdk view` prints, a team is `complete` once it can tick no more: its tick controller or engine ended it, or it has played `maxTicks`. The session is `complete` once every team is. A game that ends by its limit used to read `complete: false` after its last round. Undoing that round clears it again; a `getPastState` entry's `complete` still reports only a controller or engine ending the game
|
|
183
|
+
- **The SDK's `react` and `react-dom` peers are optional.** Only `@seriousgaming/sdk/ui` needs them, so a project that imports the SDK without React, such as a runtime running the conformance suite, no longer gets peer warnings
|
|
184
|
+
- **The conformance suite reaches every endpoint check through a surface.** Eight checks called with no surface, a caller only an authoring host offers; they now call from the participant or session UI, so a host with no outside route runs them. A runtime may treat a resource's `rootDirectory` as empty when the archive holds no directory there, which an asset-less scenario packed by npm or a zip tool does not
|
|
185
|
+
|
|
181
186
|
### Removed
|
|
182
187
|
|
|
183
188
|
- **`sdk docs ask` and `sdk docs search`**, with the `@xenova/transformers` dependency behind them (about 230MB less to install) and `HF_HOME` and `HUGGINGFACE_HUB_CACHE`. Read the shipped docs with `sdk docs view <path>`, search `node_modules/@seriousgaming/sdk-tools/dist/docs`, or copy them out with `sdk docs export`. Both commands stay as hidden pointers that say so and fail with `USAGE_REMOVED_COMMAND` (exit 2), so agent guidance that still names them finds its way. Delete the `sharp` and `protobufjs` overrides from your `pnpm-workspace.yaml`
|
|
@@ -12,7 +12,7 @@ The codes are the contract: branch on the code, never on the message. A code is
|
|
|
12
12
|
| --------- | ------------- | ------------------- | ----- |
|
|
13
13
|
| 1 | `internal` | Internal error | 1 |
|
|
14
14
|
| 2 | `usage` | Usage error | 15 |
|
|
15
|
-
| 3 | `package` | Invalid package |
|
|
15
|
+
| 3 | `package` | Invalid package | 29 |
|
|
16
16
|
| 4 | `refused` | Refused by the game | 11 |
|
|
17
17
|
| 5 | `environment` | Environment | 18 |
|
|
18
18
|
|
|
@@ -73,6 +73,7 @@ A code marked as a diagnostic also appears in a failure's `diagnostics`, one fin
|
|
|
73
73
|
| `LOGIC_EXPORT_MISSING` | A logic file is missing, or does not export the functions its resource needs. | Yes |
|
|
74
74
|
| `READ_NEVER_WRITTEN` | A warning: a resource reads a capability nothing in the package writes, so it only ever holds its defaults. | Yes |
|
|
75
75
|
| `NO_TERMINATION` | A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it. | Yes |
|
|
76
|
+
| `SCENARIO_EDITOR_UNREACHABLE` | A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it. | Yes |
|
|
76
77
|
| `EDITOR_ENDPOINT_STATEFUL` | The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have. | Yes |
|
|
77
78
|
| `EXTERNAL_REQUEST_NOT_OBJECT` | An external endpoint's `requestSchema` does not declare `"type": "object"` at the top, which a caller outside the game needs to pass named arguments. | Yes |
|
|
78
79
|
| `ENDPOINT_UNREQUESTED` | A warning: an internal endpoint no user interface lists in its `endpoints`. | Yes |
|
|
@@ -144,6 +145,7 @@ The codes a diagnostic carries. `LOGIC_ERROR` is only ever a diagnostic: it sits
|
|
|
144
145
|
| `LOGIC_BUNDLE_FAILED` | A logic file does not bundle: a syntax error, or an import that does not resolve. |
|
|
145
146
|
| `READ_NEVER_WRITTEN` | A warning: a resource reads a capability nothing in the package writes, so it only ever holds its defaults. |
|
|
146
147
|
| `NO_TERMINATION` | A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it. |
|
|
148
|
+
| `SCENARIO_EDITOR_UNREACHABLE` | A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it. |
|
|
147
149
|
| `EDITOR_ENDPOINT_STATEFUL` | The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have. |
|
|
148
150
|
| `EXTERNAL_REQUEST_NOT_OBJECT` | An external endpoint's `requestSchema` does not declare `"type": "object"` at the top, which a caller outside the game needs to pass named arguments. |
|
|
149
151
|
| `ENDPOINT_UNREQUESTED` | A warning: an internal endpoint no user interface lists in its `endpoints`. |
|
|
@@ -232,6 +232,7 @@ Without `--json`, each diagnostic that names a file is printed under the error a
|
|
|
232
232
|
| `LOGIC_BUNDLE_FAILED` | A logic file does not bundle: a syntax error, or an import that does not resolve. |
|
|
233
233
|
| `READ_NEVER_WRITTEN` | A warning: a resource reads a capability nothing in the package writes, so it only ever holds its defaults. |
|
|
234
234
|
| `NO_TERMINATION` | A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it. |
|
|
235
|
+
| `SCENARIO_EDITOR_UNREACHABLE` | A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it. |
|
|
235
236
|
| `EDITOR_ENDPOINT_STATEFUL` | The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have. |
|
|
236
237
|
| `EXTERNAL_REQUEST_NOT_OBJECT` | An external endpoint's `requestSchema` does not declare `"type": "object"` at the top, which a caller outside the game needs to pass named arguments. |
|
|
237
238
|
| `ENDPOINT_UNREQUESTED` | A warning: an internal endpoint no user interface lists in its `endpoints`. |
|
|
@@ -440,6 +440,8 @@ pnpm run test -- --watch
|
|
|
440
440
|
pnpm run test -- ui/src/App.test.tsx
|
|
441
441
|
```
|
|
442
442
|
|
|
443
|
+
In a repository holding many games, running every package's tests back to back can load a laptop heavily. Vitest starts a worker per core by default; cap it with `--maxWorkers`, for example `pnpm run test -- --maxWorkers=1`, when you sweep the whole repository.
|
|
444
|
+
|
|
443
445
|
## The runtime your tests do not reproduce
|
|
444
446
|
|
|
445
447
|
An engine test imports `logic.ts` straight into Vitest, which runs under **Node**, with Node's full set of globals. Your game's production runtime is a **bare V8 isolate** with only the ECMAScript intrinsics (see [the language environment your logic runs in](./engine.md#the-language-environment-your-logic-runs-in)).
|
|
@@ -185,6 +185,10 @@ export declare const ERROR_CODES: {
|
|
|
185
185
|
readonly category: "package";
|
|
186
186
|
readonly description: "A warning: the game declares neither `maxTicks` nor a tick controller, so only its engine can end it.";
|
|
187
187
|
};
|
|
188
|
+
readonly SCENARIO_EDITOR_UNREACHABLE: {
|
|
189
|
+
readonly category: "package";
|
|
190
|
+
readonly description: "A warning: the game ships a scenario editor but does not set `customScenarios.supported`, so no host ever opens it.";
|
|
191
|
+
};
|
|
188
192
|
readonly EDITOR_ENDPOINT_STATEFUL: {
|
|
189
193
|
readonly category: "package";
|
|
190
194
|
readonly description: "The scenario editor lists an endpoint that declares `reads`, `writes` or `actions`, which need a session the editor does not have.";
|
|
@@ -328,7 +332,7 @@ export type ErrorCode = keyof typeof ERROR_CODES;
|
|
|
328
332
|
* failure's `error.code` says what kind of failure it was; its diagnostics say
|
|
329
333
|
* what was found and where. LOGIC_ERROR is only ever a diagnostic.
|
|
330
334
|
*/
|
|
331
|
-
export declare const DIAGNOSTIC_CODES: readonly ["PACKAGE_INVALID", "SCHEMA_INVALID", "JSON_INVALID", "BUNDLE_MISSING", "REMOVED_FIELD", "LAYOUT_OUTDATED", "FEATURE_UNSUPPORTED", "REFERENCE_UNKNOWN", "SETTINGS_UNKNOWN", "SETTINGS_UNKNOWN_KEY", "SETTINGS_INVALID", "SETTINGS_RESERVED", "SETTINGS_LOCKED", "GAME_CONSTRAINT_INVALID", "GAME_CONSTRAINT_VIOLATED", "LOGIC_EXPORT_MISSING", "LOGIC_NODE_BUILTIN", "LOGIC_BUNDLE_FAILED", "READ_NEVER_WRITTEN", "NO_TERMINATION", "EDITOR_ENDPOINT_STATEFUL", "EXTERNAL_REQUEST_NOT_OBJECT", "ENDPOINT_UNREQUESTED", "LOGIC_ERROR"];
|
|
335
|
+
export declare const DIAGNOSTIC_CODES: readonly ["PACKAGE_INVALID", "SCHEMA_INVALID", "JSON_INVALID", "BUNDLE_MISSING", "REMOVED_FIELD", "LAYOUT_OUTDATED", "FEATURE_UNSUPPORTED", "REFERENCE_UNKNOWN", "SETTINGS_UNKNOWN", "SETTINGS_UNKNOWN_KEY", "SETTINGS_INVALID", "SETTINGS_RESERVED", "SETTINGS_LOCKED", "GAME_CONSTRAINT_INVALID", "GAME_CONSTRAINT_VIOLATED", "LOGIC_EXPORT_MISSING", "LOGIC_NODE_BUILTIN", "LOGIC_BUNDLE_FAILED", "READ_NEVER_WRITTEN", "NO_TERMINATION", "SCENARIO_EDITOR_UNREACHABLE", "EDITOR_ENDPOINT_STATEFUL", "EXTERNAL_REQUEST_NOT_OBJECT", "ENDPOINT_UNREQUESTED", "LOGIC_ERROR"];
|
|
332
336
|
export type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
|
|
333
337
|
export declare function categoryOf(code: ErrorCode): ErrorCategory;
|
|
334
338
|
export declare function exitCodeOf(code: ErrorCode): number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seriousgaming/sdk-tools",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "SDK tooling for building Serious Gaming games",
|
|
5
5
|
"homepage": "https://sdk.seriousgaming.com/",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@mui/icons-material": "^9.4.0",
|
|
69
69
|
"@mui/material": "^9.4.0",
|
|
70
70
|
"@playwright/test": "^1.64.0",
|
|
71
|
-
"@seriousgaming/sdk": "1.0.0-alpha.
|
|
71
|
+
"@seriousgaming/sdk": "1.0.0-alpha.2",
|
|
72
72
|
"@testing-library/jest-dom": "^7.0.1",
|
|
73
73
|
"@testing-library/react": "^16.3.3",
|
|
74
74
|
"@types/adm-zip": "^0.5.8",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"vitest": "^5.0.3"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@seriousgaming/sdk": "^1.0.0-alpha.
|
|
101
|
+
"@seriousgaming/sdk": "^1.0.0-alpha.2"
|
|
102
102
|
},
|
|
103
103
|
"engines": {
|
|
104
104
|
"node": ">=24"
|
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
"test": "vitest",
|
|
116
116
|
"test:run": "vitest run && vitest run --config cli/dev/console/vitest.config.ts",
|
|
117
117
|
"test:coverage": "vitest run --coverage",
|
|
118
|
+
"test:run:coverage": "vitest run --coverage && vitest run --config cli/dev/console/vitest.config.ts",
|
|
118
119
|
"test:e2e": "pnpm run test:e2e:cli && pnpm run test:integration && pnpm run test:e2e:ui",
|
|
119
120
|
"test:e2e:cli": "vitest run --config e2e/vitest.e2e.config.ts",
|
|
120
121
|
"test:integration": "vitest run --config integration/vitest.integration.config.ts",
|