@seriousgaming/sdk 0.0.0-stage → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +1 -0
  3. package/README.md +33 -2
  4. package/lib/build/callbacks.d.ts +998 -0
  5. package/lib/build/callbacks.js +32 -0
  6. package/lib/build/context-merge.d.ts +59 -0
  7. package/lib/build/context-merge.js +118 -0
  8. package/lib/build/document.d.ts +36 -0
  9. package/lib/build/document.js +8 -0
  10. package/lib/build/encoding.d.ts +26 -0
  11. package/lib/build/encoding.js +78 -0
  12. package/lib/build/endpoints.d.ts +421 -0
  13. package/lib/build/endpoints.js +46 -0
  14. package/lib/build/hooks.d.ts +74 -0
  15. package/lib/build/hooks.js +1 -0
  16. package/lib/build/index.d.ts +18 -0
  17. package/lib/build/index.js +16 -0
  18. package/lib/build/llm.d.ts +166 -0
  19. package/lib/build/llm.js +17 -0
  20. package/lib/build/resource-schema.d.ts +1048 -0
  21. package/lib/build/resource-schema.js +7 -0
  22. package/lib/build/scheduling.d.ts +112 -0
  23. package/lib/build/scheduling.js +217 -0
  24. package/lib/build/seed.d.ts +51 -0
  25. package/lib/build/seed.js +89 -0
  26. package/lib/build/signatures.d.ts +372 -0
  27. package/lib/build/signatures.js +1 -0
  28. package/lib/build/speech.d.ts +102 -0
  29. package/lib/build/speech.js +11 -0
  30. package/lib/build/tick-validation.d.ts +23 -0
  31. package/lib/build/tick-validation.js +96 -0
  32. package/lib/build/timer.d.ts +74 -0
  33. package/lib/build/timer.js +1 -0
  34. package/lib/build/validators.d.ts +116 -0
  35. package/lib/build/validators.js +1 -0
  36. package/lib/commands.d.ts +357 -0
  37. package/lib/commands.js +14 -0
  38. package/lib/e2e.d.ts +238 -0
  39. package/lib/e2e.js +248 -0
  40. package/lib/projection.d.ts +266 -0
  41. package/lib/projection.js +23 -0
  42. package/lib/testing/index.d.ts +13 -0
  43. package/lib/testing/index.js +13 -0
  44. package/lib/testing/logic-args.d.ts +177 -0
  45. package/lib/testing/logic-args.js +229 -0
  46. package/lib/testing/stub-services.d.ts +41 -0
  47. package/lib/testing/stub-services.js +176 -0
  48. package/lib/ui/CrossTeamDataContext.d.ts +36 -0
  49. package/lib/ui/CrossTeamDataContext.js +32 -0
  50. package/lib/ui/PresentationContext.d.ts +72 -0
  51. package/lib/ui/PresentationContext.js +13 -0
  52. package/lib/ui/PresentationWrapper.d.ts +12 -0
  53. package/lib/ui/PresentationWrapper.js +78 -0
  54. package/lib/ui/SessionUserInterfaceCallbacksContext.d.ts +3 -0
  55. package/lib/ui/SessionUserInterfaceCallbacksContext.js +7 -0
  56. package/lib/ui/SessionUserInterfaceDataContext.d.ts +103 -0
  57. package/lib/ui/SessionUserInterfaceDataContext.js +17 -0
  58. package/lib/ui/SessionUserInterfaceHookFailuresContext.d.ts +44 -0
  59. package/lib/ui/SessionUserInterfaceHookFailuresContext.js +15 -0
  60. package/lib/ui/SessionUserInterfaceHostOverlaysContext.d.ts +23 -0
  61. package/lib/ui/SessionUserInterfaceHostOverlaysContext.js +23 -0
  62. package/lib/ui/SessionUserInterfaceLocaleContext.d.ts +11 -0
  63. package/lib/ui/SessionUserInterfaceLocaleContext.js +13 -0
  64. package/lib/ui/SessionUserInterfaceMetaContext.d.ts +39 -0
  65. package/lib/ui/SessionUserInterfaceMetaContext.js +13 -0
  66. package/lib/ui/SessionUserInterfaceParticipantsContext.d.ts +12 -0
  67. package/lib/ui/SessionUserInterfaceParticipantsContext.js +16 -0
  68. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.d.ts +53 -0
  69. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.js +23 -0
  70. package/lib/ui/SessionUserInterfaceTeamLinksContext.d.ts +56 -0
  71. package/lib/ui/SessionUserInterfaceTeamLinksContext.js +12 -0
  72. package/lib/ui/SessionUserInterfaceTeamsContext.d.ts +13 -0
  73. package/lib/ui/SessionUserInterfaceTeamsContext.js +13 -0
  74. package/lib/ui/SessionUserInterfaceThemeContext.d.ts +9 -0
  75. package/lib/ui/SessionUserInterfaceThemeContext.js +7 -0
  76. package/lib/ui/SessionUserInterfaceThemePreferenceContext.d.ts +3 -0
  77. package/lib/ui/SessionUserInterfaceThemePreferenceContext.js +7 -0
  78. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.d.ts +42 -0
  79. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.js +26 -0
  80. package/lib/ui/SessionUserInterfaceWrapper.d.ts +8 -0
  81. package/lib/ui/SessionUserInterfaceWrapper.js +321 -0
  82. package/lib/ui/SettingsEditorCallbacksContext.d.ts +3 -0
  83. package/lib/ui/SettingsEditorCallbacksContext.js +7 -0
  84. package/lib/ui/SettingsEditorContext.d.ts +33 -0
  85. package/lib/ui/SettingsEditorContext.js +16 -0
  86. package/lib/ui/SettingsEditorLocaleContext.d.ts +13 -0
  87. package/lib/ui/SettingsEditorLocaleContext.js +15 -0
  88. package/lib/ui/SettingsEditorThemeContext.d.ts +9 -0
  89. package/lib/ui/SettingsEditorThemeContext.js +7 -0
  90. package/lib/ui/SettingsEditorThemePreferenceContext.d.ts +3 -0
  91. package/lib/ui/SettingsEditorThemePreferenceContext.js +7 -0
  92. package/lib/ui/SettingsEditorWrapper.d.ts +13 -0
  93. package/lib/ui/SettingsEditorWrapper.js +74 -0
  94. package/lib/ui/UserInterfaceCallbacksContext.d.ts +3 -0
  95. package/lib/ui/UserInterfaceCallbacksContext.js +7 -0
  96. package/lib/ui/UserInterfaceDataContext.d.ts +78 -0
  97. package/lib/ui/UserInterfaceDataContext.js +11 -0
  98. package/lib/ui/UserInterfaceLocaleContext.d.ts +13 -0
  99. package/lib/ui/UserInterfaceLocaleContext.js +15 -0
  100. package/lib/ui/UserInterfaceOwnTeamContext.d.ts +25 -0
  101. package/lib/ui/UserInterfaceOwnTeamContext.js +26 -0
  102. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.d.ts +52 -0
  103. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.js +13 -0
  104. package/lib/ui/UserInterfaceParticipantContext.d.ts +16 -0
  105. package/lib/ui/UserInterfaceParticipantContext.js +24 -0
  106. package/lib/ui/UserInterfaceParticipantsContext.d.ts +12 -0
  107. package/lib/ui/UserInterfaceParticipantsContext.js +13 -0
  108. package/lib/ui/UserInterfaceRunKindContext.d.ts +31 -0
  109. package/lib/ui/UserInterfaceRunKindContext.js +31 -0
  110. package/lib/ui/UserInterfaceTeamsContext.d.ts +12 -0
  111. package/lib/ui/UserInterfaceTeamsContext.js +13 -0
  112. package/lib/ui/UserInterfaceThemeContext.d.ts +9 -0
  113. package/lib/ui/UserInterfaceThemeContext.js +7 -0
  114. package/lib/ui/UserInterfaceThemePreferenceContext.d.ts +3 -0
  115. package/lib/ui/UserInterfaceThemePreferenceContext.js +7 -0
  116. package/lib/ui/UserInterfaceTimerContext.d.ts +10 -0
  117. package/lib/ui/UserInterfaceTimerContext.js +12 -0
  118. package/lib/ui/UserInterfaceValidationContext.d.ts +25 -0
  119. package/lib/ui/UserInterfaceValidationContext.js +14 -0
  120. package/lib/ui/UserInterfaceViewerRoleContext.d.ts +14 -0
  121. package/lib/ui/UserInterfaceViewerRoleContext.js +17 -0
  122. package/lib/ui/UserInterfaceWrapper.d.ts +8 -0
  123. package/lib/ui/UserInterfaceWrapper.js +219 -0
  124. package/lib/ui/expectedParentOrigin.d.ts +10 -0
  125. package/lib/ui/expectedParentOrigin.js +32 -0
  126. package/lib/ui/index.d.ts +50 -0
  127. package/lib/ui/index.js +52 -0
  128. package/lib/ui/internal/SurfaceGate.d.ts +19 -0
  129. package/lib/ui/internal/SurfaceGate.js +17 -0
  130. package/lib/ui/internal/SurfaceScope.d.ts +17 -0
  131. package/lib/ui/internal/SurfaceScope.js +16 -0
  132. package/lib/ui/internal/createContexts.d.ts +50 -0
  133. package/lib/ui/internal/createContexts.js +32 -0
  134. package/lib/ui/internal/document-attributes.d.ts +9 -0
  135. package/lib/ui/internal/document-attributes.js +87 -0
  136. package/lib/ui/internal/surface-timer.d.ts +3 -0
  137. package/lib/ui/internal/surface-timer.js +11 -0
  138. package/lib/ui/internal/useLatestRef.d.ts +7 -0
  139. package/lib/ui/internal/useLatestRef.js +11 -0
  140. package/lib/ui/internal/useOptimisticMerge.d.ts +19 -0
  141. package/lib/ui/internal/useOptimisticMerge.js +28 -0
  142. package/lib/ui/internal/useSurfaceControls.d.ts +52 -0
  143. package/lib/ui/internal/useSurfaceControls.js +19 -0
  144. package/lib/ui/internal/useTypedPastState.d.ts +17 -0
  145. package/lib/ui/internal/useTypedPastState.js +17 -0
  146. package/lib/ui/mutation-commit.d.ts +32 -0
  147. package/lib/ui/mutation-commit.js +1 -0
  148. package/lib/ui/mutation-outcome.d.ts +66 -0
  149. package/lib/ui/mutation-outcome.js +49 -0
  150. package/lib/ui/request-id.d.ts +18 -0
  151. package/lib/ui/request-id.js +44 -0
  152. package/lib/ui/testing.d.ts +365 -0
  153. package/lib/ui/testing.js +572 -0
  154. package/lib/ui/tick-snapshot.d.ts +28 -0
  155. package/lib/ui/tick-snapshot.js +34 -0
  156. package/lib/ui/ui-protocol.d.ts +799 -0
  157. package/lib/ui/ui-protocol.js +50 -0
  158. package/lib/ui/useAsset.d.ts +80 -0
  159. package/lib/ui/useAsset.js +139 -0
  160. package/lib/ui/useEndpointCallbacks.d.ts +31 -0
  161. package/lib/ui/useEndpointCallbacks.js +68 -0
  162. package/lib/ui/useHostChannel.d.ts +90 -0
  163. package/lib/ui/useHostChannel.js +338 -0
  164. package/lib/ui/useMutationReporter.d.ts +100 -0
  165. package/lib/ui/useMutationReporter.js +97 -0
  166. package/lib/ui/usePastState.d.ts +39 -0
  167. package/lib/ui/usePastState.js +63 -0
  168. package/lib/ui/useViewerStorage.d.ts +54 -0
  169. package/lib/ui/useViewerStorage.js +141 -0
  170. package/package.json +89 -3
@@ -0,0 +1,372 @@
1
+ import { Participant, SignatureCallbacks } from './callbacks.js';
2
+ import type { ContextDeletePath } from './context-merge.js';
3
+ import type { GameTeamMode } from './resource-schema.js';
4
+ /**
5
+ * Which kind of run a function is executing in.
6
+ *
7
+ * `'session'` is the game being played. `'walkthrough'` is a disposable run
8
+ * alongside it, used to demonstrate the game before or during play: one team,
9
+ * one participant, always from tick 0, isolated from the session it sits
10
+ * beside.
11
+ *
12
+ * A walkthrough is a solo run of something built for a group. Logic may use
13
+ * this to synthesise the social context that is genuinely absent (other teams
14
+ * on a leaderboard, teammates in a presence list), and to suppress spoilers and
15
+ * side effects. It must not change the mechanics of the team being driven: a
16
+ * walkthrough that plays differently teaches the wrong game.
17
+ */
18
+ export type RunKind = 'session' | 'walkthrough';
19
+ /**
20
+ * How a session runs its teams (§5.2): the game's `teams.mode`.
21
+ *
22
+ * Only the two the game declares, and no third for a session that happens to
23
+ * hold one team: §20.1 admits only these, and a one-team session is read off
24
+ * `teams.length`, not off the mode.
25
+ */
26
+ export type SessionMode = GameTeamMode;
27
+ /**
28
+ * Which viewer a player surface is being rendered for (§13.3).
29
+ *
30
+ * - `'participant'`: a team member who joined the session. Has a caller
31
+ * identity and appears in the team roster.
32
+ * - `'observer'`: a facilitator peeking at a team's live player surface without
33
+ * joining. Has no caller identity and is absent from the roster and
34
+ * presence. Still sees the live surface and, on a permissive host, may
35
+ * drive it.
36
+ */
37
+ export type ViewerRole = 'participant' | 'observer';
38
+ /**
39
+ * The facts true of the whole run, delivered to every call into game logic
40
+ * whether it runs inside the tick loop or outside it.
41
+ *
42
+ * {@link RunMeta} adds the tick for logic that runs as part of one. Hooks,
43
+ * endpoints and validators receive this shape alone: none of them runs as part
44
+ * of a tick, so there is no tick being executed to name.
45
+ */
46
+ export interface LogicMeta {
47
+ /**
48
+ * The active session locale (e.g. 'en', 'ar'). Session-wide, not per
49
+ * participant: every participant in a session shares one locale. Use it
50
+ * when generating prose (for example via `callbacks.generateText`) that
51
+ * should be produced in the session's language.
52
+ */
53
+ locale: string;
54
+ /**
55
+ * A session-wide random seed, stable for the life of the session and across
56
+ * a re-run of the same tick.
57
+ *
58
+ * Logic functions may be invoked more than once for one logical tick, so a
59
+ * live `Math.random()` is unsafe: two runs of the same tick disagree and
60
+ * only the last is durable.
61
+ *
62
+ * Derive instead. {@link deriveSeed} combines this with whatever identifies
63
+ * the thing being seeded, and {@link seededRandom} turns the result into a
64
+ * generator:
65
+ *
66
+ * ```ts
67
+ * const random = seededRandom(deriveSeed(meta.seed, meta.index, 'opening-hand'));
68
+ * ```
69
+ *
70
+ * Freeze a derived value into context if later ticks need the same one,
71
+ * rather than re-deriving it from parts that may have changed.
72
+ */
73
+ seed: string;
74
+ /**
75
+ * Whether this is the game being played or a walkthrough beside it.
76
+ *
77
+ * Host-supplied and identical on every surface of one run, so a decision
78
+ * the engine takes on it and a decision a user interface takes on it cannot
79
+ * disagree. A host that predates walkthroughs reports `'session'`, which is
80
+ * also the value every existing game behaves correctly on.
81
+ *
82
+ * ```ts
83
+ * const rivals = meta.runKind === 'walkthrough'
84
+ * ? demonstrationRivals(meta.seed)
85
+ * : context.market.rivals;
86
+ * ```
87
+ */
88
+ runKind: RunKind;
89
+ /**
90
+ * When this operation started, in milliseconds since the epoch: the tick,
91
+ * the hook dispatch, the endpoint call or the validator run this call is
92
+ * part of. Fixed by the host before the first logic call of the operation,
93
+ * so every call in it (every team's `Run` in one tick, and the tick
94
+ * controller after it) reads the same value, and a re-run of the same
95
+ * operation reads it again.
96
+ *
97
+ * Use it instead of `Date.now()`, which differs between a tick and its
98
+ * re-run and is not offered by every runtime (§3.2):
99
+ *
100
+ * ```ts
101
+ * const placedAt = meta.now;
102
+ * const ageMs = meta.now - context.orders.PlacedAt;
103
+ * ```
104
+ *
105
+ * Freeze it into context if a later operation needs the same instant.
106
+ */
107
+ now: number;
108
+ }
109
+ /**
110
+ * `meta` for logic that runs as part of a tick: the engine's `Init` and `Run`,
111
+ * the aggregate forms of both, and tick-controller logic.
112
+ */
113
+ export interface RunMeta extends LogicMeta {
114
+ /**
115
+ * The tick being executed, resolved before the function runs: `0` in
116
+ * `Init`, and 1-based in `Run`, so the first `Run` of a session sees `1`
117
+ * and the last `Run` of a `ticks: 10` game sees `10`.
118
+ *
119
+ * Deliberately one ahead of the `completedTicks` a user interface reads,
120
+ * which counts the ticks already completed (§9.3).
121
+ */
122
+ tick: number;
123
+ }
124
+ /**
125
+ * `meta` for a function that runs against one team's context: `Init`, `Run`,
126
+ * and tick-controller logic.
127
+ *
128
+ * The rule is one sentence: **`teamId` and `index` name the team whose context
129
+ * is in `context`.** In independent mode that is the team being ticked. In
130
+ * shared-state mode the tick controller runs once for the session with team 0's
131
+ * context, so it reads team 0's identity, which is what it was given.
132
+ *
133
+ * `RunAggregate` and `InitAggregate` receive plain {@link RunMeta}: they run
134
+ * across every team at once, so there is no single team to name, and each
135
+ * team's identity is already on its entry in `teams`.
136
+ */
137
+ export interface TeamRunMeta extends RunMeta {
138
+ /**
139
+ * The team this call is running for, e.g. `'team-1'`. The same identifier
140
+ * `RunAggregate` sees in its `teams` array, and the same one a session
141
+ * surface keys `SessionData` by. Opaque and stable for the session: never
142
+ * parse it or build one from an index, and key ordinal logic on `index`
143
+ * instead (§5.6). A single-team game's one team has an ordinary id too:
144
+ * one id space, so a value keyed off this in the engine can be looked up
145
+ * by a surface.
146
+ */
147
+ teamId: string;
148
+ /**
149
+ * The team's stable, session-lifetime index. Assigned at team creation
150
+ * (dense 0..N-1 at session start) and never reassigned: a removal leaves a
151
+ * gap, and a team added later takes the lowest unused index. Use it, not
152
+ * array position, to key anything per team.
153
+ */
154
+ index: number;
155
+ }
156
+ export interface LogicFunctionArgs<TContext, TSettings, TCallbacks = SignatureCallbacks> {
157
+ context: TContext;
158
+ /**
159
+ * The settings resources this resource declared in `settings`, keyed by
160
+ * identifier: resolved from their defaults, the active scenario and the
161
+ * session's overrides, and fixed when play starts (§8). Read-only.
162
+ */
163
+ settings: TSettings;
164
+ meta: TeamRunMeta;
165
+ /**
166
+ * The participants on the team this call is running for, taken when the
167
+ * operation started (§10.2). A tick controller sees the participants of
168
+ * every team in its `teams`. Read-only, and a copy per call, so changing
169
+ * it changes nothing: only a lifecycle operation (a join, a rename, a
170
+ * move) changes the roster.
171
+ *
172
+ * ```ts
173
+ * const present = participants.filter((p) => p.connected).length;
174
+ * ```
175
+ */
176
+ participants: readonly Readonly<Participant>[];
177
+ callbacks: TCallbacks;
178
+ }
179
+ /**
180
+ * What engine and hook logic returns: the state it writes, named by kind.
181
+ *
182
+ * `capabilities` is a patch over the context the function received, merged one
183
+ * level deep, exactly as the whole result was before it was wrapped. Naming the
184
+ * kind leaves room for other outputs beside it, and the runtime refuses any
185
+ * top-level key it does not know, so a result written in the old flat shape
186
+ * fails rather than losing its writes.
187
+ */
188
+ export interface LogicResult<TContext> {
189
+ capabilities: TContext;
190
+ /**
191
+ * Paths to remove once `capabilities` is folded in, with the same rules as
192
+ * a UI's `deletes`: segments joined by `.` (`'orders.Open.o-17'`), or an
193
+ * array of segments where a key could contain a dot. A path that does not
194
+ * resolve is not an error. Each must sit under a capability the resource
195
+ * declares in `writes`; one under a capability it only reads fails the
196
+ * call. Use it to drop an entry from a keyed map without rebuilding the
197
+ * whole variable.
198
+ */
199
+ deletes?: ContextDeletePath[];
200
+ }
201
+ /**
202
+ * The arguments of the engine's own functions: everything logic receives, plus
203
+ * the engine's private memory.
204
+ *
205
+ * `state` is whatever the engine last returned under `state`, and `undefined`
206
+ * until it first returns one (so always in `Init`). The engine is its only
207
+ * writer and its only reader: no surface, hook, validator or endpoint ever
208
+ * sees it. It is snapshotted and rewound with the tick, so undo and reset put
209
+ * it back exactly as they put back capabilities.
210
+ */
211
+ export interface EngineFunctionArgs<TContext, TSettings, TCallbacks = SignatureCallbacks, TState = never> extends LogicFunctionArgs<TContext, TSettings, TCallbacks> {
212
+ /**
213
+ * Optional in the type so a test calling `Run` directly need not pass it;
214
+ * the runtime always does, as `undefined` before the first write.
215
+ */
216
+ state?: TState;
217
+ }
218
+ /**
219
+ * What the engine's `Init` and `Run` return: the capabilities they write and,
220
+ * optionally, the engine's private memory.
221
+ *
222
+ * `state` replaces the previous state whole; leave it out to keep the previous
223
+ * one. It must be JSON-serialisable (plain objects, arrays, strings, finite
224
+ * numbers, booleans and `null`), which the runtime checks.
225
+ *
226
+ * Typed by `TState` rather than a schema: the engine is the only writer and the
227
+ * only reader, so the compiler checks it end to end.
228
+ */
229
+ export interface EngineResult<TContext, TState = never> extends LogicResult<TContext> {
230
+ state?: TState;
231
+ }
232
+ export type InitializationFunction<TContext, TSettings, TCallbacks = SignatureCallbacks, TState = never> = (args: EngineFunctionArgs<TContext, TSettings, TCallbacks, TState>) => Promise<EngineResult<TContext, TState>>;
233
+ /**
234
+ * What the engine's `Run` returns: an {@link EngineResult}, plus whether the
235
+ * game is now over for this team.
236
+ */
237
+ export interface RunResult<TContext, TState = never> extends EngineResult<TContext, TState> {
238
+ /**
239
+ * `true` ends the game for the team this call ran for: no further ticks,
240
+ * as a tick controller's `complete` does. Per team in independent mode;
241
+ * in shared-state mode one team's completion ends it for every team,
242
+ * because they share one clock. An undo of the completing tick clears it.
243
+ * `false` and absence are the same: completion cannot be withdrawn here.
244
+ */
245
+ complete?: boolean;
246
+ }
247
+ export type RunFunction<TContext, TSettings, TCallbacks = SignatureCallbacks, TState = never> = (args: EngineFunctionArgs<TContext, TSettings, TCallbacks, TState>) => Promise<RunResult<TContext, TState>>;
248
+ /**
249
+ * One step of an engine that splits its `Init` or `Run` into several functions.
250
+ *
251
+ * A step takes the engine's arguments and returns the next context, flat, so
252
+ * steps chain by passing one's output to the next; only the exported `Init` or
253
+ * `Run` wraps the final context in `{ capabilities }`, and returns any `state`.
254
+ * A step may read `state`; its type defaults to `unknown`, so a step that
255
+ * ignores it accepts whatever the engine keeps.
256
+ */
257
+ export type StepFunction<TContext, TSettings, TCallbacks = SignatureCallbacks, TState = unknown> = (args: EngineFunctionArgs<TContext, TSettings, TCallbacks, TState>) => Promise<TContext>;
258
+ export interface TickControllerResult {
259
+ /** False disables automatic ticking: ticks are manual only. */
260
+ enabled: boolean;
261
+ /**
262
+ * Milliseconds until the next automatic tick, a whole number within the
263
+ * §11.8 bounds (30 seconds to 7 days). Needed when `enabled` is true and
264
+ * there is no `nextTickAt`; beside a `nextTickAt` it is only a display
265
+ * hint, and a controller with the timer off may leave it out.
266
+ */
267
+ intervalMs?: number;
268
+ /**
269
+ * When the next tick should fire (epoch milliseconds, UTC). When set and
270
+ * `enabled` is true, the runtime uses it instead of `intervalMs`. If it is
271
+ * in the past the runtime fires one tick immediately, then re-evaluates.
272
+ */
273
+ nextTickAt?: number;
274
+ /** When true, the game is permanently complete: no further ticks will be allowed. */
275
+ complete?: boolean;
276
+ }
277
+ export interface TickControllerFunctionArgs<TContext, TSettings, TCallbacks = SignatureCallbacks> extends LogicFunctionArgs<TContext, TSettings, TCallbacks> {
278
+ /**
279
+ * The teams this evaluation covers, sorted by stable index: every team in
280
+ * a shared-state game, the one team being evaluated in independent mode,
281
+ * and the one team in a single-team game. Always present, so a controller
282
+ * can say which team it is waiting on by `teamId` or `index` even after a
283
+ * team has been removed and the indexes have a gap.
284
+ */
285
+ teams: AggregateFunctionTeam<TContext>[];
286
+ /**
287
+ * Whether the session's waiting room is currently open — players are seeing
288
+ * the game's waiting room UI instead of its game UIs.
289
+ *
290
+ * The flag pauses nothing by itself. A game that wants automatic ticks to
291
+ * stop while players wait returns `enabled: false` when this is true;
292
+ * a game whose simulation should keep advancing in the background ignores
293
+ * it. Deciding here rather than in the runtime keeps the policy with the
294
+ * author, who is the only one who knows which of the two their game is.
295
+ */
296
+ waitingRoomOpen: boolean;
297
+ }
298
+ export type GetTickControllerFunction<TContext, TSettings, TCallbacks = SignatureCallbacks> = (args: TickControllerFunctionArgs<TContext, TSettings, TCallbacks>) => Promise<TickControllerResult>;
299
+ export interface AggregateFunctionTeam<TTeamContext, TTeamState = never> {
300
+ teamId: string;
301
+ /**
302
+ * Platform-assigned, session-stable team index. Assigned at team creation
303
+ * (dense 0..N-1 at session start) and never reassigned: removals leave a
304
+ * gap, later-added teams take the lowest unused index. The same team has
305
+ * the same index here, in the player teams context, and in the
306
+ * session teams context. `teams.defaultNames` entries are applied
307
+ * by this index.
308
+ */
309
+ index: number;
310
+ /**
311
+ * This team's context. For an aggregate engine function, its own
312
+ * capabilities (team and private scope): shared-scope capabilities are not
313
+ * repeated here, and arrive once, in `shared`. For a tick controller, the
314
+ * team's context as the controller's own `context` would hold it.
315
+ */
316
+ context: TTeamContext;
317
+ /** This team's engine state, as `Run` sees it (see {@link EngineFunctionArgs}). */
318
+ state?: TTeamState;
319
+ }
320
+ /**
321
+ * The session's shared store, as an aggregate function sees it: one value per
322
+ * shared-scope capability the engine declared, whatever the number of teams.
323
+ */
324
+ export interface AggregateFunctionShared<TSharedContext, TSharedState = never> {
325
+ context: TSharedContext;
326
+ /**
327
+ * The engine's session-wide private memory: one value for the session,
328
+ * written under `shared.state` by an aggregate function in a shared-state
329
+ * or single-team session.
330
+ */
331
+ state?: TSharedState;
332
+ }
333
+ export interface RunAggregateFunctionArgs<TTeamContext, TSharedContext, TSettings, TCallbacks = SignatureCallbacks, TTeamState = never, TSharedState = never> {
334
+ shared: AggregateFunctionShared<TSharedContext, TSharedState>;
335
+ teams: AggregateFunctionTeam<TTeamContext, TTeamState>[];
336
+ /**
337
+ * The settings resources this resource declared in `settings`, keyed by
338
+ * identifier: resolved from their defaults, the active scenario and the
339
+ * session's overrides, and fixed when play starts (§8). Read-only.
340
+ */
341
+ settings: TSettings;
342
+ meta: RunMeta;
343
+ /**
344
+ * The participants of every team in `teams`, taken when the operation
345
+ * started (§10.2): every participant in the session whenever every team
346
+ * is passed. Group by `teamId` to join them to their team. Read-only.
347
+ */
348
+ participants: readonly Readonly<Participant>[];
349
+ callbacks: TCallbacks;
350
+ }
351
+ /**
352
+ * An aggregate engine's result. Each level names what it writes the same way a
353
+ * team's `Run` does: every `teams` entry has the shape of a `Run` result, in
354
+ * the order the teams were passed, and `shared` carries the session's
355
+ * shared-scope capabilities. A shared capability is written through `shared`
356
+ * alone; one in a `teams` entry fails the call.
357
+ */
358
+ export interface RunAggregateResult<TTeamContext, TSharedContext, TTeamState = never, TSharedState = never> {
359
+ shared: EngineResult<Partial<TSharedContext>, TSharedState>;
360
+ teams: EngineResult<TTeamContext, TTeamState>[];
361
+ }
362
+ /**
363
+ * `RunAggregate`'s result: each level may also report completion, as `Run`'s
364
+ * `complete` does. A `teams` entry's ends the game for that team; `shared`'s
365
+ * for every team at once.
366
+ */
367
+ export interface RunAggregateTickResult<TTeamContext, TSharedContext, TTeamState = never, TSharedState = never> {
368
+ shared: RunResult<Partial<TSharedContext>, TSharedState>;
369
+ teams: RunResult<TTeamContext, TTeamState>[];
370
+ }
371
+ export type RunAggregateFunction<TTeamContext, TSharedContext, TSettings, TCallbacks = SignatureCallbacks, TTeamState = never, TSharedState = never> = (args: RunAggregateFunctionArgs<TTeamContext, TSharedContext, TSettings, TCallbacks, TTeamState, TSharedState>) => Promise<RunAggregateTickResult<TTeamContext, TSharedContext, TTeamState, TSharedState>>;
372
+ export type InitAggregateFunction<TTeamContext, TSharedContext, TSettings, TCallbacks = SignatureCallbacks, TTeamState = never, TSharedState = never> = (args: RunAggregateFunctionArgs<TTeamContext, TSharedContext, TSettings, TCallbacks, TTeamState, TSharedState>) => Promise<RunAggregateResult<TTeamContext, TSharedContext, TTeamState, TSharedState>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Speech Generation Types
3
+ *
4
+ * Provides provider-agnostic text-to-speech for the engine, hooks, tick controllers
5
+ * and user interfaces. Game code describes the utterance via a small set of
6
+ * role-based voice presets; the runtime decides which provider, model and
7
+ * voice executes the call. The synthesised audio is persisted as a blob and
8
+ * the returned asset reference (`blob:<id>`) can be played from a UI
9
+ * (`getAssetUrl`) or read in the engine (`readAsset`).
10
+ */
11
+ import type { LLMTier } from './llm.js';
12
+ /**
13
+ * Role-based voice preset. The runtime maps the preset to a concrete provider
14
+ * voice. Game code only ever passes the preset name. The five values below are
15
+ * the recommended starting set; runtimes may accept additional preset names.
16
+ *
17
+ * - `neutral` — warm, balanced, unobtrusive
18
+ * - `narrator` — engaging, gravitas, draws the listener in
19
+ * - `announcer` — clear, authoritative, broadcast-style
20
+ * - `executive` — confident, weighty, commands attention
21
+ * - `coach` — encouraging, warm, reassuring
22
+ *
23
+ * If a requested preset is not configured, the runtime falls back to
24
+ * `neutral`; if that is also unconfigured, it falls back to the provider's
25
+ * own default voice.
26
+ */
27
+ export type GenerateSpeechVoice = 'neutral' | 'narrator' | 'announcer' | 'executive' | 'coach';
28
+ /**
29
+ * Audio container format for synthesised speech.
30
+ */
31
+ export type GenerateSpeechFormat = 'mp3' | 'wav' | 'opus';
32
+ /**
33
+ * Options for synthesising speech.
34
+ */
35
+ export interface GenerateSpeechRequest {
36
+ /**
37
+ * The text to read aloud. Plain text — SSML and provider-specific tags
38
+ * are intentionally not supported in v1.
39
+ */
40
+ text: string;
41
+ /**
42
+ * Voice preset name. Defaults to `'neutral'`. See {@link GenerateSpeechVoice}.
43
+ * Any string is accepted; unknown presets fall back to `neutral`.
44
+ */
45
+ voice?: GenerateSpeechVoice | string;
46
+ /**
47
+ * Capability tier (`quick` / `standard` / `expert`). Mirrors {@link LLMTier};
48
+ * the runtime maps each tier to a provider model. Defaults to `'quick'`.
49
+ */
50
+ tier?: LLMTier;
51
+ /**
52
+ * Output audio container. Defaults to `'mp3'`.
53
+ */
54
+ format?: GenerateSpeechFormat;
55
+ /**
56
+ * Optional BCP-47 language hint (e.g. `'en-GB'`, `'fr-FR'`). Advisory; the
57
+ * provider may ignore it.
58
+ */
59
+ languageHint?: string;
60
+ /**
61
+ * Playback speed multiplier in the range `0.5` to `2.0`. The runtime
62
+ * clamps to whatever range the configured provider supports.
63
+ */
64
+ speed?: number;
65
+ }
66
+ /**
67
+ * Response from a speech-synthesis call. The audio bytes are persisted as a
68
+ * blob; play them from a UI via `<audio src={callbacks.getAssetUrl(reference)} />`
69
+ * or read the raw bytes in the engine via `callbacks.readAsset(reference)`.
70
+ */
71
+ export interface GenerateSpeechResponse {
72
+ /**
73
+ * Asset reference for the synthesised audio, of the form `blob:<id>`.
74
+ * Resolve via `getAssetUrl` (UI) or `readAsset` (engine).
75
+ */
76
+ reference: string;
77
+ /**
78
+ * MIME type of the audio (e.g. `'audio/mpeg'`, `'audio/wav'`, `'audio/ogg'`).
79
+ */
80
+ mimeType: string;
81
+ /**
82
+ * Best-effort audio duration in milliseconds. Not all providers return this.
83
+ */
84
+ durationMs?: number;
85
+ /**
86
+ * Number of characters the provider billed for this synthesis. Useful for
87
+ * observability and budgeting.
88
+ */
89
+ charactersBilled?: number;
90
+ }
91
+ /**
92
+ * Function for synthesising speech.
93
+ *
94
+ * Can be called with a bare string (shorthand for `{ text: '...' }`) or a
95
+ * full options object:
96
+ *
97
+ * ```typescript
98
+ * await callbacks.generateSpeech('Welcome to the simulation.');
99
+ * await callbacks.generateSpeech({ text: '...', voice: 'narrator' });
100
+ * ```
101
+ */
102
+ export type GenerateSpeechFunction = (request: GenerateSpeechRequest | string) => Promise<GenerateSpeechResponse>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Speech Generation Types
3
+ *
4
+ * Provides provider-agnostic text-to-speech for the engine, hooks, tick controllers
5
+ * and user interfaces. Game code describes the utterance via a small set of
6
+ * role-based voice presets; the runtime decides which provider, model and
7
+ * voice executes the call. The synthesised audio is persisted as a blob and
8
+ * the returned asset reference (`blob:<id>`) can be played from a UI
9
+ * (`getAssetUrl`) or read in the engine (`readAsset`).
10
+ */
11
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { TickControllerResult } from './signatures.js';
2
+ /**
3
+ * Options for {@link validateTickControllerResult}.
4
+ */
5
+ export interface ValidateTickResultOptions {
6
+ /**
7
+ * Override for "now" in ms since epoch, used to evaluate the effective
8
+ * cadence of a scheduled (`nextTickAt`) result. Defaults to `Date.now()`.
9
+ */
10
+ now?: number;
11
+ }
12
+ /**
13
+ * Validate a runtime `TickControllerResult` against the §11.8–§11.9 contract.
14
+ *
15
+ * Returns `null` when the result is valid, or a human-readable error message
16
+ * that names the limit, cites SPECIFICATION.md §11, and points at the helper to
17
+ * use (never at an exported symbol). The message is suitable for surfacing to
18
+ * authors at authoring time, in the dev server, or in the build step.
19
+ *
20
+ * The builders in `scheduling.ts` call this, so an invalid result is refused
21
+ * at authoring time rather than at the first tick.
22
+ */
23
+ export declare function validateTickControllerResult(result: Pick<TickControllerResult, 'intervalMs' | 'nextTickAt'>, options?: ValidateTickResultOptions): string | null;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Effective tick interval bounds, derived from SPECIFICATION.md §11.8: every
3
+ * conformant runtime guarantees a 30-second floor and a 7-day ceiling on the
4
+ * *effective cadence* (the gap until the next automatic tick), not merely on
5
+ * the `intervalMs` display hint.
6
+ *
7
+ * These constants are deliberately module-private. Authors build valid results
8
+ * with `intervalTick()` / `scheduledTick()`, and the contract surface for
9
+ * runtime implementers is the spec, never an exported magic number.
10
+ *
11
+ * This module is internal to the SDK: it is what the builders validate with,
12
+ * and no entry point exports it. The runtime's copy lives in
13
+ * `@seriousgaming/sdk-tools/runtime`, and
14
+ * `tick-validation.parity.test.ts` there holds the two to one table of cases.
15
+ */
16
+ const MIN_EFFECTIVE_TICK_INTERVAL_MS = 30_000;
17
+ const MAX_EFFECTIVE_TICK_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000; // 604,800,000 (7 days)
18
+ /**
19
+ * Validate a runtime `TickControllerResult` against the §11.8–§11.9 contract.
20
+ *
21
+ * Returns `null` when the result is valid, or a human-readable error message
22
+ * that names the limit, cites SPECIFICATION.md §11, and points at the helper to
23
+ * use (never at an exported symbol). The message is suitable for surfacing to
24
+ * authors at authoring time, in the dev server, or in the build step.
25
+ *
26
+ * The builders in `scheduling.ts` call this, so an invalid result is refused
27
+ * at authoring time rather than at the first tick.
28
+ */
29
+ export function validateTickControllerResult(result, options) {
30
+ const { intervalMs, nextTickAt } = result;
31
+ if (nextTickAt !== undefined &&
32
+ (typeof nextTickAt !== 'number' || !Number.isFinite(nextTickAt))) {
33
+ return (`Tick controller nextTickAt must be a finite timestamp in milliseconds; got ${describe(nextTickAt)}. ` +
34
+ `See the tick controller guide.`);
35
+ }
36
+ const now = options?.now ?? Date.now();
37
+ // The effective cadence is the gap the runtime will actually schedule:
38
+ // nextTickAt − now in scheduled mode, intervalMs in interval mode (§11.8).
39
+ const effectiveMs = nextTickAt !== undefined ? nextTickAt - now : (intervalMs ?? NaN);
40
+ // A nextTickAt at or in the past yields a 0 delay: firing one immediate
41
+ // catch-up tick is explicitly allowed and exempt from both the floor and the
42
+ // intervalMs shape rule, since `intervalMs` is only a display hint here
43
+ // (§11.8/§11.3). The tick controller re-evaluates after the catch-up fires.
44
+ if (nextTickAt !== undefined && effectiveMs <= 0) {
45
+ return null;
46
+ }
47
+ // Beside a nextTickAt, intervalMs is a display hint and may be absent; when
48
+ // it is there it is held to the same shape as in interval mode.
49
+ if ((nextTickAt === undefined || intervalMs !== undefined) &&
50
+ (typeof intervalMs !== 'number' ||
51
+ !Number.isFinite(intervalMs) ||
52
+ !Number.isInteger(intervalMs) ||
53
+ intervalMs < 1)) {
54
+ return (`Tick intervalMs must be a finite, positive, whole number of milliseconds; got ${describe(intervalMs)}. ` +
55
+ `Build interval-based results with intervalTick(intervalMs). See the tick controller guide.`);
56
+ }
57
+ if (effectiveMs < MIN_EFFECTIVE_TICK_INTERVAL_MS) {
58
+ return (`Effective tick cadence of ${duration(effectiveMs)} is below the 30-second minimum ` +
59
+ `(${MIN_EFFECTIVE_TICK_INTERVAL_MS} ms). Use intervalTick(intervalMs) with at least ` +
60
+ `${MIN_EFFECTIVE_TICK_INTERVAL_MS}, or schedule nextTickAt further out. See the tick controller guide.`);
61
+ }
62
+ if (effectiveMs > MAX_EFFECTIVE_TICK_INTERVAL_MS) {
63
+ return (`Effective tick cadence of ${duration(effectiveMs)} exceeds the 7-day maximum ` +
64
+ `(${MAX_EFFECTIVE_TICK_INTERVAL_MS} ms). For longer gaps, set enabled: false and ` +
65
+ `drive ticks by another means. See the tick controller guide.`);
66
+ }
67
+ return null;
68
+ }
69
+ function describe(value) {
70
+ if (value === undefined)
71
+ return 'undefined';
72
+ if (typeof value === 'number') {
73
+ if (Number.isNaN(value))
74
+ return 'NaN';
75
+ if (!Number.isFinite(value))
76
+ return value > 0 ? 'Infinity' : '-Infinity';
77
+ return String(value);
78
+ }
79
+ return typeof value;
80
+ }
81
+ /** `12000 ms (12 seconds)`: the field's own unit, then a reading of it. */
82
+ function duration(ms) {
83
+ const rounded = Number.isInteger(ms) ? String(ms) : ms.toFixed(1);
84
+ const seconds = ms / 1000;
85
+ const human = seconds >= 86_400
86
+ ? `${round(seconds / 86_400)} days`
87
+ : seconds >= 3_600
88
+ ? `${round(seconds / 3_600)} hours`
89
+ : seconds >= 1
90
+ ? `${round(seconds)} seconds`
91
+ : 'under a second';
92
+ return `${rounded} ms (${human})`;
93
+ }
94
+ function round(value) {
95
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
96
+ }