@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,7 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated by json-schema-to-typescript.
4
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5
+ * and run json-schema-to-typescript to regenerate this file.
6
+ */
7
+ export {};
@@ -0,0 +1,112 @@
1
+ import type { TickControllerResult } from './signatures.js';
2
+ /**
3
+ * Options accepted by all schedule-computation helpers.
4
+ */
5
+ export interface ScheduleOptions {
6
+ /**
7
+ * IANA timezone name (e.g. `'Australia/Sydney'`, `'America/New_York'`).
8
+ * Defaults to `'UTC'`. DST transitions are handled automatically via
9
+ * `Intl.DateTimeFormat`.
10
+ */
11
+ timezone?: string;
12
+ /**
13
+ * Override for "now" in ms since epoch — useful for deterministic tests.
14
+ * Defaults to `Date.now()`.
15
+ */
16
+ now?: number;
17
+ }
18
+ /**
19
+ * Compute the next occurrence of a daily time.
20
+ *
21
+ * @param hours Hour of the day (0–23)
22
+ * @param minutes Minute of the hour (0–59)
23
+ * @param options Optional timezone and `now` override
24
+ * @returns Unix timestamp in milliseconds of the next occurrence
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Next midnight UTC
29
+ * nextDailyAt(0, 0);
30
+ *
31
+ * // Next midnight in Sydney (handles DST)
32
+ * nextDailyAt(0, 0, { timezone: 'Australia/Sydney' });
33
+ * ```
34
+ */
35
+ export declare function nextDailyAt(hours: number, minutes: number, options?: ScheduleOptions): number;
36
+ /**
37
+ * Compute the next occurrence of a weekly time.
38
+ *
39
+ * @param dayOfWeek 0 = Sunday, 1 = Monday, …, 6 = Saturday
40
+ * @param hours Hour of the day (0–23)
41
+ * @param minutes Minute of the hour (0–59)
42
+ * @param options Optional timezone and `now` override
43
+ * @returns Unix timestamp in milliseconds of the next occurrence
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * // Next Monday at 09:00 UTC
48
+ * nextWeeklyAt(1, 9, 0);
49
+ *
50
+ * // Next Friday at 17:00 in New York
51
+ * nextWeeklyAt(5, 17, 0, { timezone: 'America/New_York' });
52
+ * ```
53
+ */
54
+ export declare function nextWeeklyAt(dayOfWeek: number, hours: number, minutes: number, options?: ScheduleOptions): number;
55
+ /**
56
+ * Pick the soonest future time from a list of daily times.
57
+ *
58
+ * @param times Array of `{ hours, minutes }` pairs
59
+ * @param options Optional timezone and `now` override
60
+ * @returns Unix timestamp in milliseconds of the soonest next occurrence
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // Next occurrence of 09:00 or 17:00 UTC
65
+ * nextDailyFromSchedule([
66
+ * { hours: 9, minutes: 0 },
67
+ * { hours: 17, minutes: 0 },
68
+ * ]);
69
+ * ```
70
+ */
71
+ export declare function nextDailyFromSchedule(times: Array<{
72
+ hours: number;
73
+ minutes: number;
74
+ }>, options?: ScheduleOptions): number;
75
+ /**
76
+ * Convenience builder that creates a `TickControllerResult` for scheduled
77
+ * (absolute-time) ticking.
78
+ *
79
+ * `intervalMs` is auto-computed as milliseconds-until-`nextTickAt`, so a
80
+ * surface drawing a progress bar has the window's full length to draw against.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * return scheduledTick(nextDailyAt(0, 0, { timezone: settings.engine.Timezone }));
85
+ * ```
86
+ */
87
+ export declare function scheduledTick(nextTickAt: number, options?: {
88
+ complete?: boolean;
89
+ }): TickControllerResult;
90
+ /**
91
+ * Convenience builder that creates a `TickControllerResult` for interval-based
92
+ * (cadence) ticking — the sanctioned way to return an interval result.
93
+ *
94
+ * `intervalMs` is in **milliseconds** and must be a finite, positive, whole
95
+ * number within the §11.8 effective interval bounds (a 30-second floor, 30000,
96
+ * and a 7-day ceiling, 604800000). Out-of-range, fractional, `NaN`,
97
+ * `Infinity`, zero, and negative values are rejected with a clear message, so
98
+ * a bad cadence (or a value written in seconds) is caught at authoring time
99
+ * rather than at runtime.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * // Tick every 30 seconds
104
+ * return intervalTick(30_000);
105
+ *
106
+ * // Tick once a minute, and mark the game complete
107
+ * return intervalTick(60_000, { complete: true });
108
+ * ```
109
+ */
110
+ export declare function intervalTick(intervalMs: number, options?: {
111
+ complete?: boolean;
112
+ }): TickControllerResult;
@@ -0,0 +1,217 @@
1
+ import { validateTickControllerResult } from './tick-validation.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Internal helpers
4
+ // ---------------------------------------------------------------------------
5
+ /**
6
+ * Return the wall-clock components (year, month, day, hours, minutes) in
7
+ * the given IANA timezone. Uses `Intl.DateTimeFormat` so no third-party
8
+ * timezone library is needed.
9
+ */
10
+ function wallClock(epochMs, timezone) {
11
+ const fmt = new Intl.DateTimeFormat('en-US', {
12
+ timeZone: timezone,
13
+ year: 'numeric',
14
+ month: '2-digit',
15
+ day: '2-digit',
16
+ hour: '2-digit',
17
+ minute: '2-digit',
18
+ weekday: 'short',
19
+ hour12: false,
20
+ });
21
+ const parts = Object.fromEntries(fmt.formatToParts(new Date(epochMs)).map((p) => [p.type, p.value]));
22
+ const weekdayMap = {
23
+ Sun: 0,
24
+ Mon: 1,
25
+ Tue: 2,
26
+ Wed: 3,
27
+ Thu: 4,
28
+ Fri: 5,
29
+ Sat: 6,
30
+ };
31
+ return {
32
+ year: Number(parts.year),
33
+ month: Number(parts.month),
34
+ day: Number(parts.day),
35
+ hours: Number(parts.hour),
36
+ minutes: Number(parts.minute),
37
+ weekday: weekdayMap[parts.weekday] ?? 0,
38
+ };
39
+ }
40
+ /**
41
+ * Build a UTC epoch-ms timestamp for a specific wall-clock date/time in the
42
+ * given timezone. Works by creating a candidate UTC date, checking what
43
+ * wall-clock time that maps to, and compensating for the difference.
44
+ */
45
+ function toEpoch(year, month, day, hours, minutes, timezone) {
46
+ // Treat the target date/time as if it were UTC
47
+ const naiveUtc = Date.UTC(year, month - 1, day, hours, minutes, 0, 0);
48
+ // What wall-clock reading does naiveUtc produce in the target timezone?
49
+ const wall = wallClock(naiveUtc, timezone);
50
+ // Build a "naive UTC" for that wall-clock reading so we can compute the
51
+ // full offset including any day difference.
52
+ const wallNaiveUtc = Date.UTC(wall.year, wall.month - 1, wall.day, wall.hours, wall.minutes, 0, 0);
53
+ // The offset is the difference: positive means the timezone is ahead of UTC.
54
+ // Subtract it to get the true UTC instant for the target wall-clock time.
55
+ return naiveUtc - (wallNaiveUtc - naiveUtc);
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Public API
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Compute the next occurrence of a daily time.
62
+ *
63
+ * @param hours Hour of the day (0–23)
64
+ * @param minutes Minute of the hour (0–59)
65
+ * @param options Optional timezone and `now` override
66
+ * @returns Unix timestamp in milliseconds of the next occurrence
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Next midnight UTC
71
+ * nextDailyAt(0, 0);
72
+ *
73
+ * // Next midnight in Sydney (handles DST)
74
+ * nextDailyAt(0, 0, { timezone: 'Australia/Sydney' });
75
+ * ```
76
+ */
77
+ export function nextDailyAt(hours, minutes, options) {
78
+ const tz = options?.timezone ?? 'UTC';
79
+ const now = options?.now ?? Date.now();
80
+ const wall = wallClock(now, tz);
81
+ // Try today first
82
+ let target = toEpoch(wall.year, wall.month, wall.day, hours, minutes, tz);
83
+ // If that's not in the future, use tomorrow
84
+ if (target <= now) {
85
+ const tomorrow = new Date(Date.UTC(wall.year, wall.month - 1, wall.day + 1));
86
+ const tw = wallClock(tomorrow.getTime(), tz);
87
+ target = toEpoch(tw.year, tw.month, tw.day, hours, minutes, tz);
88
+ }
89
+ return target;
90
+ }
91
+ /**
92
+ * Compute the next occurrence of a weekly time.
93
+ *
94
+ * @param dayOfWeek 0 = Sunday, 1 = Monday, …, 6 = Saturday
95
+ * @param hours Hour of the day (0–23)
96
+ * @param minutes Minute of the hour (0–59)
97
+ * @param options Optional timezone and `now` override
98
+ * @returns Unix timestamp in milliseconds of the next occurrence
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * // Next Monday at 09:00 UTC
103
+ * nextWeeklyAt(1, 9, 0);
104
+ *
105
+ * // Next Friday at 17:00 in New York
106
+ * nextWeeklyAt(5, 17, 0, { timezone: 'America/New_York' });
107
+ * ```
108
+ */
109
+ export function nextWeeklyAt(dayOfWeek, hours, minutes, options) {
110
+ const tz = options?.timezone ?? 'UTC';
111
+ const now = options?.now ?? Date.now();
112
+ const wall = wallClock(now, tz);
113
+ // Days until the target day-of-week
114
+ let daysAhead = (dayOfWeek - wall.weekday + 7) % 7;
115
+ // If it's the same weekday, check if the time has passed
116
+ if (daysAhead === 0) {
117
+ const todayTarget = toEpoch(wall.year, wall.month, wall.day, hours, minutes, tz);
118
+ if (todayTarget > now) {
119
+ return todayTarget;
120
+ }
121
+ daysAhead = 7;
122
+ }
123
+ const targetDate = new Date(Date.UTC(wall.year, wall.month - 1, wall.day + daysAhead));
124
+ const tw = wallClock(targetDate.getTime(), tz);
125
+ return toEpoch(tw.year, tw.month, tw.day, hours, minutes, tz);
126
+ }
127
+ /**
128
+ * Pick the soonest future time from a list of daily times.
129
+ *
130
+ * @param times Array of `{ hours, minutes }` pairs
131
+ * @param options Optional timezone and `now` override
132
+ * @returns Unix timestamp in milliseconds of the soonest next occurrence
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * // Next occurrence of 09:00 or 17:00 UTC
137
+ * nextDailyFromSchedule([
138
+ * { hours: 9, minutes: 0 },
139
+ * { hours: 17, minutes: 0 },
140
+ * ]);
141
+ * ```
142
+ */
143
+ export function nextDailyFromSchedule(times, options) {
144
+ if (times.length === 0) {
145
+ throw new Error('nextDailyFromSchedule requires at least one time entry');
146
+ }
147
+ let earliest = Infinity;
148
+ for (const { hours, minutes } of times) {
149
+ const t = nextDailyAt(hours, minutes, options);
150
+ if (t < earliest)
151
+ earliest = t;
152
+ }
153
+ return earliest;
154
+ }
155
+ /**
156
+ * Convenience builder that creates a `TickControllerResult` for scheduled
157
+ * (absolute-time) ticking.
158
+ *
159
+ * `intervalMs` is auto-computed as milliseconds-until-`nextTickAt`, so a
160
+ * surface drawing a progress bar has the window's full length to draw against.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * return scheduledTick(nextDailyAt(0, 0, { timezone: settings.engine.Timezone }));
165
+ * ```
166
+ */
167
+ export function scheduledTick(nextTickAt, options) {
168
+ const now = Date.now();
169
+ const intervalMs = Math.max(0, Math.ceil(nextTickAt - now));
170
+ const result = {
171
+ enabled: true,
172
+ ...(intervalMs >= 1 ? { intervalMs } : {}),
173
+ nextTickAt,
174
+ ...(options?.complete !== undefined ? { complete: options.complete } : {}),
175
+ };
176
+ // Enforce the §11.8 bounds on the effective cadence (nextTickAt − now), not
177
+ // just on the intervalMs display hint, so a sub-floor or over-ceiling
178
+ // schedule is rejected at authoring time rather than silently honoured at
179
+ // runtime.
180
+ const error = validateTickControllerResult(result, { now });
181
+ if (error) {
182
+ throw new Error(error);
183
+ }
184
+ return result;
185
+ }
186
+ /**
187
+ * Convenience builder that creates a `TickControllerResult` for interval-based
188
+ * (cadence) ticking — the sanctioned way to return an interval result.
189
+ *
190
+ * `intervalMs` is in **milliseconds** and must be a finite, positive, whole
191
+ * number within the §11.8 effective interval bounds (a 30-second floor, 30000,
192
+ * and a 7-day ceiling, 604800000). Out-of-range, fractional, `NaN`,
193
+ * `Infinity`, zero, and negative values are rejected with a clear message, so
194
+ * a bad cadence (or a value written in seconds) is caught at authoring time
195
+ * rather than at runtime.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * // Tick every 30 seconds
200
+ * return intervalTick(30_000);
201
+ *
202
+ * // Tick once a minute, and mark the game complete
203
+ * return intervalTick(60_000, { complete: true });
204
+ * ```
205
+ */
206
+ export function intervalTick(intervalMs, options) {
207
+ const result = {
208
+ enabled: true,
209
+ intervalMs,
210
+ ...(options?.complete !== undefined ? { complete: options.complete } : {}),
211
+ };
212
+ const error = validateTickControllerResult(result);
213
+ if (error) {
214
+ throw new Error(error);
215
+ }
216
+ return result;
217
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Deterministic randomness for engine logic.
3
+ *
4
+ * Two constraints shape these. First, engine functions may run more than once
5
+ * for one logical tick, so a live `Math.random()` produces a value that
6
+ * disagrees between runs and only survives if it happened to be in the last
7
+ * one. Second, logic bundles run against the ECMAScript intrinsics alone:
8
+ * there is no `crypto`, no `TextEncoder`, and nothing else to hash with. So the
9
+ * hash is written out here, in the SDK, rather than assumed to exist.
10
+ *
11
+ * The pairing is deliberate. `deriveSeed` turns 'this session, this team, this
12
+ * purpose' into a stable seed; `seededRandom` turns a seed into numbers. A seed
13
+ * on its own does not give you randomness, which is why shipping only
14
+ * `meta.seed` would have left every author writing this file.
15
+ */
16
+ /**
17
+ * Derive a stable seed from a session seed and whatever identifies the thing
18
+ * being seeded.
19
+ *
20
+ * ```ts
21
+ * const seed = deriveSeed(meta.seed, meta.index, 'opening-hand');
22
+ * ```
23
+ *
24
+ * The same parts always give the same result, in this process and in any other,
25
+ * so a re-run of a tick derives the same value and two teams derive different
26
+ * ones. Parts are joined with a separator that cannot appear in a stringified
27
+ * number, so `deriveSeed(s, 1, 23)` and `deriveSeed(s, 12, 3)` cannot collide.
28
+ *
29
+ * This is a mixing function, not a cryptographic hash. It is for dealing hands
30
+ * and placing customers; it is not for anything an adversary should be unable
31
+ * to predict.
32
+ */
33
+ export declare function deriveSeed(...parts: (string | number)[]): string;
34
+ /**
35
+ * A seeded pseudo-random generator: call it for the next value in `[0, 1)`,
36
+ * like `Math.random()`.
37
+ *
38
+ * ```ts
39
+ * const random = seededRandom(deriveSeed(meta.seed, meta.index));
40
+ * const price = 10 + Math.floor(random() * 5);
41
+ * ```
42
+ *
43
+ * The sequence depends only on the seed, so the same seed always replays the
44
+ * same numbers. Draw a fresh generator per purpose rather than threading one
45
+ * through a tick: two independent sequences are easier to reason about than one
46
+ * whose values depend on how many times something else was called first.
47
+ *
48
+ * mulberry32: one 32-bit state word, good statistical quality for simulation,
49
+ * and short enough to read. Not for anything security-sensitive.
50
+ */
51
+ export declare function seededRandom(seed: string): () => number;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Deterministic randomness for engine logic.
3
+ *
4
+ * Two constraints shape these. First, engine functions may run more than once
5
+ * for one logical tick, so a live `Math.random()` produces a value that
6
+ * disagrees between runs and only survives if it happened to be in the last
7
+ * one. Second, logic bundles run against the ECMAScript intrinsics alone:
8
+ * there is no `crypto`, no `TextEncoder`, and nothing else to hash with. So the
9
+ * hash is written out here, in the SDK, rather than assumed to exist.
10
+ *
11
+ * The pairing is deliberate. `deriveSeed` turns 'this session, this team, this
12
+ * purpose' into a stable seed; `seededRandom` turns a seed into numbers. A seed
13
+ * on its own does not give you randomness, which is why shipping only
14
+ * `meta.seed` would have left every author writing this file.
15
+ */
16
+ /**
17
+ * Derive a stable seed from a session seed and whatever identifies the thing
18
+ * being seeded.
19
+ *
20
+ * ```ts
21
+ * const seed = deriveSeed(meta.seed, meta.index, 'opening-hand');
22
+ * ```
23
+ *
24
+ * The same parts always give the same result, in this process and in any other,
25
+ * so a re-run of a tick derives the same value and two teams derive different
26
+ * ones. Parts are joined with a separator that cannot appear in a stringified
27
+ * number, so `deriveSeed(s, 1, 23)` and `deriveSeed(s, 12, 3)` cannot collide.
28
+ *
29
+ * This is a mixing function, not a cryptographic hash. It is for dealing hands
30
+ * and placing customers; it is not for anything an adversary should be unable
31
+ * to predict.
32
+ */
33
+ export function deriveSeed(...parts) {
34
+ const joined = parts.map((part) => String(part)).join('\u001f');
35
+ // Two FNV-1a passes from different offset bases, concatenated. A single
36
+ // 32-bit value starts colliding noticeably once a game derives tens of
37
+ // thousands of seeds (the birthday bound is near 2^16); widening it pushes
38
+ // that past anything a session reaches. The passes share a multiplier, so
39
+ // this is wider rather than twice as strong, which is all it needs to be.
40
+ const low = fnv1a(joined, 0x811c9dc5);
41
+ const high = fnv1a(joined, 0x01000193);
42
+ return toHex(high) + toHex(low);
43
+ }
44
+ /**
45
+ * A seeded pseudo-random generator: call it for the next value in `[0, 1)`,
46
+ * like `Math.random()`.
47
+ *
48
+ * ```ts
49
+ * const random = seededRandom(deriveSeed(meta.seed, meta.index));
50
+ * const price = 10 + Math.floor(random() * 5);
51
+ * ```
52
+ *
53
+ * The sequence depends only on the seed, so the same seed always replays the
54
+ * same numbers. Draw a fresh generator per purpose rather than threading one
55
+ * through a tick: two independent sequences are easier to reason about than one
56
+ * whose values depend on how many times something else was called first.
57
+ *
58
+ * mulberry32: one 32-bit state word, good statistical quality for simulation,
59
+ * and short enough to read. Not for anything security-sensitive.
60
+ */
61
+ export function seededRandom(seed) {
62
+ let state = fnv1a(seed, 0x811c9dc5);
63
+ return () => {
64
+ state = (state + 0x6d2b79f5) >>> 0;
65
+ let mixed = state;
66
+ mixed = Math.imul(mixed ^ (mixed >>> 15), mixed | 1);
67
+ mixed ^= mixed + Math.imul(mixed ^ (mixed >>> 7), mixed | 61);
68
+ return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296;
69
+ };
70
+ }
71
+ /**
72
+ * FNV-1a over the string's UTF-16 code units.
73
+ *
74
+ * Code units rather than UTF-8 bytes because there is no encoder to reach for
75
+ * in the logic environment, and the choice does not matter: the requirement is
76
+ * that the same string always mixes to the same number, not that it matches
77
+ * any other implementation of FNV.
78
+ */
79
+ function fnv1a(value, offsetBasis) {
80
+ let hash = offsetBasis >>> 0;
81
+ for (let i = 0; i < value.length; i++) {
82
+ hash ^= value.charCodeAt(i);
83
+ hash = Math.imul(hash, 0x01000193) >>> 0;
84
+ }
85
+ return hash >>> 0;
86
+ }
87
+ function toHex(value) {
88
+ return (value >>> 0).toString(16).padStart(8, '0');
89
+ }