@dreamboard-games/sdk 0.4.0-alpha.7 → 0.4.0-alpha.9

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 (112) hide show
  1. package/REFERENCE.md +1292 -287
  2. package/dist/authoring/index.d.ts +4 -9
  3. package/dist/authoring/index.js +11 -547
  4. package/dist/authoring/index.js.map +1 -1
  5. package/dist/authoring-compiler.d.ts +27 -0
  6. package/dist/authoring-compiler.js +12 -0
  7. package/dist/authoring-compiler.js.map +1 -0
  8. package/dist/authoring-materialize-cli.d.ts +1 -0
  9. package/dist/authoring-materialize-cli.js +46 -0
  10. package/dist/authoring-materialize-cli.js.map +1 -0
  11. package/dist/browser-interaction.d.ts +1 -1
  12. package/dist/{chunk-TGMXBRKY.js → chunk-3PFDHTR7.js} +19 -7
  13. package/dist/chunk-3PFDHTR7.js.map +1 -0
  14. package/dist/chunk-45P5CEGH.js +176 -0
  15. package/dist/chunk-45P5CEGH.js.map +1 -0
  16. package/dist/chunk-5XTAQ4EQ.js +32 -0
  17. package/dist/chunk-5XTAQ4EQ.js.map +1 -0
  18. package/dist/chunk-7ADPYKFT.js +532 -0
  19. package/dist/chunk-7ADPYKFT.js.map +1 -0
  20. package/dist/{chunk-RKGJ64UN.js → chunk-BUOODBVO.js} +2 -2
  21. package/dist/{chunk-VLG4YST5.js → chunk-GMOBG7Y7.js} +4 -4
  22. package/dist/{chunk-Q322XCY2.js → chunk-JRGP6W4V.js} +3 -3
  23. package/dist/chunk-L3W2FPRQ.js +1793 -0
  24. package/dist/chunk-L3W2FPRQ.js.map +1 -0
  25. package/dist/{chunk-3QIZKXJ2.js → chunk-NGYFBNBP.js} +3 -3
  26. package/dist/chunk-P6IJ5YVY.js +154 -0
  27. package/dist/chunk-P6IJ5YVY.js.map +1 -0
  28. package/dist/{chunk-5IHU5CUK.js → chunk-QQNL4WTX.js} +25 -22
  29. package/dist/chunk-QQNL4WTX.js.map +1 -0
  30. package/dist/{chunk-52DH7VQF.js → chunk-SACZWUA6.js} +143 -47
  31. package/dist/chunk-SACZWUA6.js.map +1 -0
  32. package/dist/{chunk-KYEWGZ2Y.js → chunk-SFOAY5H4.js} +2 -2
  33. package/dist/{chunk-KYEWGZ2Y.js.map → chunk-SFOAY5H4.js.map} +1 -1
  34. package/dist/chunk-UNVCT5Z7.js +73 -0
  35. package/dist/chunk-UNVCT5Z7.js.map +1 -0
  36. package/dist/{chunk-NO537WSS.js → chunk-WDR7SKBM.js} +2 -2
  37. package/dist/chunk-XDGRC6OR.js +7039 -0
  38. package/dist/chunk-XDGRC6OR.js.map +1 -0
  39. package/dist/chunk-XLHLFTLI.js +399 -0
  40. package/dist/chunk-XLHLFTLI.js.map +1 -0
  41. package/dist/{chunk-B2YBS3SG.js → chunk-YNW3KBDE.js} +41 -13
  42. package/dist/chunk-YNW3KBDE.js.map +1 -0
  43. package/dist/codegen.js +1 -1
  44. package/dist/{components-BTwSIjXj.d.ts → components-BPzsSRs8.d.ts} +1 -1
  45. package/dist/definitions-0kjPV9wh.d.ts +294 -0
  46. package/dist/{diagnostics-1BWjRo6-.d.ts → diagnostics-DIi3ina7.d.ts} +2 -1
  47. package/dist/{index-CFTjJIXd.d.ts → index-CWzWpQzM.d.ts} +1 -1
  48. package/dist/{index.d-DMASeF6M.d.ts → index.d-C7Ztirg6.d.ts} +234 -5
  49. package/dist/index.js +2 -2
  50. package/dist/package-set.d.ts +2 -2
  51. package/dist/package-set.js +2 -2
  52. package/dist/{protocol-835hEGne.d.ts → protocol-B2LWcqB4.d.ts} +14 -0
  53. package/dist/reducer/advanced.d.ts +4 -4
  54. package/dist/reducer/advanced.js +7 -26
  55. package/dist/reducer/advanced.js.map +1 -1
  56. package/dist/reducer-contract.d.ts +1 -1
  57. package/dist/reducer-contract.js +2 -2
  58. package/dist/reducer.d.ts +37 -28
  59. package/dist/reducer.js +43 -6018
  60. package/dist/reducer.js.map +1 -1
  61. package/dist/reference-game-compiler.d.ts +45 -0
  62. package/dist/reference-game-compiler.js +25 -0
  63. package/dist/reference-game-compiler.js.map +1 -0
  64. package/dist/reference-game-source-manifest-cli.d.ts +1 -0
  65. package/dist/reference-game-source-manifest-cli.js +84 -0
  66. package/dist/reference-game-source-manifest-cli.js.map +1 -0
  67. package/dist/reference-games/index.d.ts +7 -217
  68. package/dist/reference-games/index.js +28 -208
  69. package/dist/reference-games/index.js.map +1 -1
  70. package/dist/runtime/primitives.d.ts +3 -3
  71. package/dist/runtime/primitives.js +3 -3
  72. package/dist/runtime/runtime-api.d.ts +1 -1
  73. package/dist/runtime/workspace-contract.d.ts +4 -4
  74. package/dist/runtime/workspace-contract.js +4 -4
  75. package/dist/{runtime-Dkfm1x2r.d.ts → runtime-B_w9AO2n.d.ts} +60 -5
  76. package/dist/{runtime-api-C6aWo9c0.d.ts → runtime-api-xCWLMQRT.d.ts} +2 -0
  77. package/dist/runtime.d.ts +7 -7
  78. package/dist/runtime.js +13 -13
  79. package/dist/schema-9jZzlEWZ.d.ts +220 -0
  80. package/dist/{stale-contract-artifact-error-C5AaZPJ8.d.ts → stale-contract-artifact-error-XLaweZtF.d.ts} +1 -1
  81. package/dist/testing-compiler.d.ts +36 -0
  82. package/dist/testing-compiler.js +236 -0
  83. package/dist/testing-compiler.js.map +1 -0
  84. package/dist/testing-runtime.d.ts +97 -21
  85. package/dist/testing-runtime.js +154 -95
  86. package/dist/testing-runtime.js.map +1 -1
  87. package/dist/testing.d.ts +178 -60
  88. package/dist/testing.js +641 -26
  89. package/dist/testing.js.map +1 -1
  90. package/dist/types-Bvptkukh.d.ts +122 -0
  91. package/dist/{types-aI4V9CWe.d.ts → types-_zfC_7CD.d.ts} +1 -1
  92. package/dist/types.d.ts +4 -0
  93. package/dist/ui/components.d.ts +2 -2
  94. package/dist/ui/plugin-styles.css +1 -1
  95. package/dist/{ui-contract-Ue6SywvO.d.ts → ui-contract-CUC7Lyoc.d.ts} +1 -1
  96. package/dist/ui.d.ts +3 -3
  97. package/dist/{views-32rSsfeU.d.ts → views-DE2eZxPz.d.ts} +42 -13
  98. package/package.json +22 -3
  99. package/dist/chunk-3OZMHZK3.js +0 -40
  100. package/dist/chunk-3OZMHZK3.js.map +0 -1
  101. package/dist/chunk-52DH7VQF.js.map +0 -1
  102. package/dist/chunk-5IHU5CUK.js.map +0 -1
  103. package/dist/chunk-B2YBS3SG.js.map +0 -1
  104. package/dist/chunk-IU6KGPY7.js +0 -659
  105. package/dist/chunk-IU6KGPY7.js.map +0 -1
  106. package/dist/chunk-TGMXBRKY.js.map +0 -1
  107. package/dist/types-DcADVHe9.d.ts +0 -192
  108. /package/dist/{chunk-RKGJ64UN.js.map → chunk-BUOODBVO.js.map} +0 -0
  109. /package/dist/{chunk-VLG4YST5.js.map → chunk-GMOBG7Y7.js.map} +0 -0
  110. /package/dist/{chunk-Q322XCY2.js.map → chunk-JRGP6W4V.js.map} +0 -0
  111. /package/dist/{chunk-3QIZKXJ2.js.map → chunk-NGYFBNBP.js.map} +0 -0
  112. /package/dist/{chunk-NO537WSS.js.map → chunk-WDR7SKBM.js.map} +0 -0
package/dist/testing.js CHANGED
@@ -1,24 +1,26 @@
1
1
  import {
2
2
  PluginRuntimeBoundary,
3
3
  createPluginRuntimeClient
4
- } from "./chunk-3QIZKXJ2.js";
5
- import "./chunk-VLG4YST5.js";
6
- import {
7
- BROWSER_INTERACTION_ACTUATOR_KINDS,
8
- DREAMBOARD_BROWSER_INTERACTION_PROTOCOL_VERSION,
9
- browserInteractionEffectSchema
10
- } from "./chunk-WHR5UW3F.js";
11
- import {
12
- RuntimeJsonSchema
13
- } from "./chunk-PF7L4BMG.js";
4
+ } from "./chunk-NGYFBNBP.js";
5
+ import "./chunk-GMOBG7Y7.js";
14
6
  import {
7
+ ScenarioAssertionError,
8
+ ScenarioDefinitionValidationError,
9
+ ScenarioReplayError,
10
+ advanceScenarioReplay,
11
+ assertScenario,
12
+ compareCanonicalScenarioJson,
15
13
  createExpectApi,
16
- createTestRuntime
17
- } from "./chunk-IU6KGPY7.js";
18
- import {
19
- StaleContractArtifactError,
20
- isStaleContractArtifactError
21
- } from "./chunk-3OZMHZK3.js";
14
+ createScenarioAuthoring,
15
+ digestScenarioJson,
16
+ enumerateScenarioInteractionParams,
17
+ inspectScenarioReplayAuthority,
18
+ probeScenarioCommand,
19
+ projectScenarioCommandParams,
20
+ replayScenario,
21
+ toScenarioReplayDefinition
22
+ } from "./chunk-L3W2FPRQ.js";
23
+ import "./chunk-5XTAQ4EQ.js";
22
24
  import {
23
25
  DREAMBOARD_PLUGIN_PROTOCOL,
24
26
  DREAMBOARD_PLUGIN_PROTOCOL_VERSION,
@@ -26,21 +28,627 @@ import {
26
28
  PluginProtocolStepSchema,
27
29
  PluginProtocolTapeSchema,
28
30
  computePluginActionSetVersion,
31
+ digestPluginRuntimeJson,
29
32
  materializePluginGameplayFrame
30
- } from "./chunk-5IHU5CUK.js";
33
+ } from "./chunk-QQNL4WTX.js";
34
+ import {
35
+ BROWSER_INTERACTION_ACTUATOR_KINDS,
36
+ DREAMBOARD_BROWSER_INTERACTION_PROTOCOL_VERSION,
37
+ browserInteractionEffectSchema
38
+ } from "./chunk-WHR5UW3F.js";
39
+ import {
40
+ StaleContractArtifactError,
41
+ isStaleContractArtifactError
42
+ } from "./chunk-XDGRC6OR.js";
43
+ import "./chunk-YNW3KBDE.js";
44
+ import {
45
+ RuntimeJsonSchema
46
+ } from "./chunk-PF7L4BMG.js";
47
+ import "./chunk-3PFDHTR7.js";
31
48
  import "./chunk-PZ5AY32C.js";
32
49
 
33
- // src/testing/definitions.ts
34
- function defineBase(definition) {
35
- return definition;
50
+ // src/testing/inspection/flow-diagnostic.ts
51
+ function createFlowDiagnostic(options) {
52
+ const actor = (playerId) => {
53
+ const seat = options.playerIds.indexOf(playerId);
54
+ return seat < 0 ? null : { seat, playerId };
55
+ };
56
+ const actors = (playerIds) => [...new Set(playerIds)].flatMap((playerId) => {
57
+ const resolved = actor(playerId);
58
+ return resolved ? [resolved] : [];
59
+ }).sort((left, right) => left.seat - right.seat);
60
+ const blockedBy = (options.scheduler.continuationDependencies ?? []).flatMap((dependency) => {
61
+ const waiter = actor(dependency.waiterPlayerId);
62
+ const blockers = actors(dependency.blockerPlayerIds);
63
+ return waiter && blockers.length > 0 ? [
64
+ {
65
+ actor: waiter,
66
+ blockers,
67
+ source: "scheduler"
68
+ }
69
+ ] : [];
70
+ }).sort((left, right) => left.actor.seat - right.actor.seat);
71
+ return {
72
+ phase: options.scheduler.phase,
73
+ step: options.scheduler.step,
74
+ activeActors: actors(options.scheduler.activePlayerIds),
75
+ pendingActors: actors(options.scheduler.pendingPlayerIds),
76
+ continuationWaiters: actors(
77
+ blockedBy.map((dependency) => dependency.actor.playerId)
78
+ ),
79
+ blockedBy
80
+ };
81
+ }
82
+
83
+ // src/testing/inspection/inspect-scenario.ts
84
+ var ScenarioInspectionError = class extends Error {
85
+ code;
86
+ context;
87
+ constructor(options) {
88
+ super(options.message);
89
+ this.name = "ScenarioInspectionError";
90
+ this.code = options.code;
91
+ this.context = options.context ?? {};
92
+ }
93
+ };
94
+ async function inspectScenario(options) {
95
+ assertScenarioIdentity(options.identity, options.scenario);
96
+ assertSeed(options.seed);
97
+ const selectedScenario = options.seed === void 0 ? options.scenario : {
98
+ ...options.scenario,
99
+ setup: { ...options.scenario.setup, seed: options.seed }
100
+ };
101
+ const checkpoint = options.at ?? {
102
+ segment: "given",
103
+ completed: selectedScenario.given.length
104
+ };
105
+ const replay = await replayScenario({
106
+ game: options.game,
107
+ scenario: selectedScenario,
108
+ at: checkpoint
109
+ });
110
+ const node = inspectScenarioReplayNode({
111
+ replay,
112
+ perspective: options.perspective
113
+ });
114
+ return {
115
+ schemaVersion: 1,
116
+ scenario: structuredClone(options.identity),
117
+ node,
118
+ seedSource: options.seed === void 0 ? "scenario" : "override"
119
+ };
120
+ }
121
+ function inspectScenarioReplayNode(options) {
122
+ const baseAuthority = inspectScenarioReplayAuthority({
123
+ replay: options.replay,
124
+ perspective: { kind: "spectator" }
125
+ });
126
+ const perspective = resolvePerspective(
127
+ options.perspective,
128
+ baseAuthority.scenario.setup.players,
129
+ baseAuthority.playerIds
130
+ );
131
+ const authority = inspectScenarioReplayAuthority({
132
+ replay: options.replay,
133
+ perspective: perspective.kind === "spectator" ? perspective : { kind: "player", seat: perspective.actor.seat }
134
+ });
135
+ const actor = perspective.kind === "player" ? perspective.actor : null;
136
+ const interactions = actor ? authority.interactions.map(
137
+ ({ descriptor, explanation }) => inspectInteraction(actor, descriptor, explanation)
138
+ ) : [];
139
+ const actions = actor ? authority.interactions.flatMap(
140
+ ({ explanation, actionability }) => {
141
+ if (!actionability.found || !actionability.visible || actionability.descriptor.availability.status !== "available" || actionability.inputSatisfiability?.status !== "yes") {
142
+ return [];
143
+ }
144
+ return [
145
+ {
146
+ actor,
147
+ interactionId: actionability.descriptor.interactionId,
148
+ inputs: explanation.inputs,
149
+ explanation,
150
+ hasConcreteCommand: true
151
+ }
152
+ ];
153
+ }
154
+ ) : [];
155
+ const node = {
156
+ checkpoint: authority.checkpoint,
157
+ checkpointDigest: authority.checkpointDigest,
158
+ setup: structuredClone(authority.scenario.setup),
159
+ flow: createFlowDiagnostic({
160
+ playerIds: authority.playerIds,
161
+ scheduler: authority.scheduler
162
+ }),
163
+ perspective,
164
+ publicState: authority.publicState,
165
+ view: authority.view,
166
+ interactions,
167
+ actions,
168
+ entropy: {
169
+ seed: authority.entropy.seed ?? authority.scenario.setup.seed,
170
+ draws: structuredClone(authority.entropy.draws)
171
+ },
172
+ dispatchTrace: projectDispatchTrace(
173
+ authority.commandTrace,
174
+ authority.playerIds,
175
+ perspective
176
+ )
177
+ };
178
+ return node;
179
+ }
180
+ function resolvePerspective(selector, playerCount, declaredPlayerIds) {
181
+ if (selector.kind === "spectator") return { kind: "spectator" };
182
+ const seat = selector.seat;
183
+ if (!Number.isSafeInteger(seat) || seat < 0 || seat >= playerCount) {
184
+ throw new ScenarioInspectionError({
185
+ code: "TEST_PERSPECTIVE_INVALID",
186
+ message: `Perspective seat ${String(seat)} is outside this ${playerCount}-player scenario.`,
187
+ context: {
188
+ requestedSeat: seat,
189
+ minimumSeat: 0,
190
+ maximumSeat: Math.max(0, playerCount - 1)
191
+ }
192
+ });
193
+ }
194
+ return {
195
+ kind: "player",
196
+ actor: {
197
+ seat,
198
+ playerId: declaredPlayerIds[seat] ?? `player-${seat + 1}`
199
+ }
200
+ };
201
+ }
202
+ function inspectInteraction(actor, descriptor, explanation) {
203
+ const availability = descriptor.availability ?? {
204
+ status: "blocked",
205
+ reason: "Interaction descriptor has no availability."
206
+ };
207
+ return {
208
+ actor,
209
+ interactionId: String(descriptor.interactionId ?? ""),
210
+ availability: {
211
+ status: availability.status,
212
+ ..."code" in availability && typeof availability.code === "string" ? { code: availability.code } : {},
213
+ ..."reason" in availability && typeof availability.reason === "string" ? { reason: availability.reason } : {}
214
+ },
215
+ inputs: explanation.inputs,
216
+ explanation
217
+ };
218
+ }
219
+ function projectDispatchTrace(commandTrace, playerIds, perspective) {
220
+ if (perspective.kind === "spectator") return [];
221
+ const selectedSeat = perspective.actor.seat;
222
+ return commandTrace.flatMap((entry) => {
223
+ if (entry.command.actor.seat !== selectedSeat) return [];
224
+ return entry.trace.flatMap((trace) => {
225
+ switch (trace.kind) {
226
+ case "acceptedClientInput": {
227
+ const seat = playerIds.indexOf(trace.playerId);
228
+ return seat < 0 ? [] : [
229
+ {
230
+ kind: "acceptedCommand",
231
+ actor: { seat, playerId: trace.playerId },
232
+ interactionId: trace.interactionId
233
+ }
234
+ ];
235
+ }
236
+ case "appliedInstruction":
237
+ return [
238
+ {
239
+ kind: "appliedInstruction",
240
+ instructionKind: trace.instruction
241
+ }
242
+ ];
243
+ case "rngConsumption":
244
+ return [{ kind: "entropyDraw", drawIndex: trace.drawIndex }];
245
+ }
246
+ });
247
+ });
248
+ }
249
+ function assertScenarioIdentity(identity, scenario) {
250
+ if (identity.id !== scenario.id) {
251
+ throw new ScenarioInspectionError({
252
+ code: "TEST_SCENARIO_INVALID",
253
+ message: "Scenario identity does not match the authored default export.",
254
+ context: { identityId: identity.id, scenarioId: scenario.id }
255
+ });
256
+ }
36
257
  }
37
- function defineScenario(definition) {
38
- return definition;
258
+ function assertSeed(seed) {
259
+ if (seed !== void 0 && !Number.isSafeInteger(seed)) {
260
+ throw new ScenarioInspectionError({
261
+ code: "TEST_SEED_RANGE_INVALID",
262
+ message: "Seed override must be a safe integer.",
263
+ context: { requestedSeed: seed }
264
+ });
265
+ }
266
+ }
267
+
268
+ // src/testing/exploration/cursor.ts
269
+ import { Buffer } from "buffer";
270
+ var CURSOR_VERSION = 1;
271
+ var ExploreCursorError = class extends Error {
272
+ code = "TEST_EXPLORE_CURSOR_STALE";
273
+ scenarioSourceDigest;
274
+ checkpointDigest;
275
+ constructor(options) {
276
+ super("The exploration cursor no longer matches scenario authority.");
277
+ this.name = "ExploreCursorError";
278
+ this.scenarioSourceDigest = options.scenarioSourceDigest;
279
+ this.checkpointDigest = options.checkpointDigest;
280
+ }
281
+ };
282
+ function createExploreCursor(options) {
283
+ assertOrdinal(options.nextOrdinal);
284
+ const payload = {
285
+ version: CURSOR_VERSION,
286
+ scenarioSourceDigest: options.scenario.sourceDigest,
287
+ checkpointDigest: options.checkpointDigest,
288
+ perspective: options.perspective,
289
+ seedOverride: options.seedOverride ?? null,
290
+ nextOrdinal: options.nextOrdinal
291
+ };
292
+ const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString(
293
+ "base64url"
294
+ );
295
+ const digest = digestPluginRuntimeJson(payload).slice("sha256:".length);
296
+ return `dbx1.${encoded}.${digest}`;
297
+ }
298
+ function readExploreCursor(options) {
299
+ const stale = () => {
300
+ throw new ExploreCursorError({
301
+ scenarioSourceDigest: options.scenario.sourceDigest,
302
+ checkpointDigest: options.checkpointDigest
303
+ });
304
+ };
305
+ const [prefix, encoded, digest, extra] = options.cursor.split(".");
306
+ if (prefix !== "dbx1" || !encoded || !digest || extra !== void 0) stale();
307
+ let payload;
308
+ try {
309
+ payload = JSON.parse(
310
+ Buffer.from(encoded, "base64url").toString("utf8")
311
+ );
312
+ } catch {
313
+ return stale();
314
+ }
315
+ if (digestPluginRuntimeJson(payload).slice("sha256:".length) !== digest || payload.version !== CURSOR_VERSION || payload.scenarioSourceDigest !== options.scenario.sourceDigest || payload.checkpointDigest !== options.checkpointDigest || payload.seedOverride !== (options.seedOverride ?? null) || JSON.stringify(payload.perspective) !== JSON.stringify(options.perspective)) {
316
+ return stale();
317
+ }
318
+ assertOrdinal(payload.nextOrdinal);
319
+ return payload.nextOrdinal;
320
+ }
321
+ function assertOrdinal(value) {
322
+ if (!Number.isSafeInteger(value) || value < 0) {
323
+ throw new RangeError(
324
+ "Explore cursor ordinal must be a non-negative safe integer."
325
+ );
326
+ }
327
+ }
328
+
329
+ // src/testing/exploration/explore-scenario.ts
330
+ var DEFAULT_EXPLORE_LIMIT = 50;
331
+ var MAX_EXPLORE_LIMIT = 200;
332
+ var DEFAULT_EXPLORE_EVALUATIONS = 5e3;
333
+ var MAX_EXPLORE_EVALUATIONS = 1e5;
334
+ var MAX_EXPLORE_SEEDS = 64;
335
+ var ScenarioExploreError = class extends Error {
336
+ code;
337
+ context;
338
+ constructor(options) {
339
+ super(options.message);
340
+ this.name = "ScenarioExploreError";
341
+ this.code = options.code;
342
+ this.context = options.context;
343
+ }
344
+ };
345
+ async function exploreScenario(options) {
346
+ if (options.seedRange) {
347
+ if (options.seed !== void 0 || options.cursor !== void 0 || options.limit !== void 0) {
348
+ throw new ScenarioExploreError({
349
+ code: "TEST_SEED_RANGE_INVALID",
350
+ message: "Seed-range exploration cannot be combined with a seed override, cursor, or transition-page limit.",
351
+ context: { start: options.seedRange.start, end: options.seedRange.end }
352
+ });
353
+ }
354
+ return exploreSeeds(options, options.seedRange);
355
+ }
356
+ return exploreTransitions(options);
357
+ }
358
+ async function exploreTransitions(options) {
359
+ const limit = options.limit ?? DEFAULT_EXPLORE_LIMIT;
360
+ const maxEvaluations = options.maxEvaluations ?? DEFAULT_EXPLORE_EVALUATIONS;
361
+ assertTransitionLimits(limit, maxEvaluations);
362
+ const selectedScenario = scenarioWithSeed(options.scenario, options.seed);
363
+ const checkpoint = defaultCheckpoint(selectedScenario, options.at);
364
+ const replay = await replayScenario({
365
+ game: options.game,
366
+ scenario: selectedScenario,
367
+ at: checkpoint
368
+ });
369
+ const node = inspectScenarioReplayNode({
370
+ replay,
371
+ perspective: options.perspective
372
+ });
373
+ const startOrdinal = options.cursor ? readExploreCursor({
374
+ cursor: options.cursor,
375
+ scenario: options.identity,
376
+ checkpointDigest: node.checkpointDigest,
377
+ perspective: node.perspective,
378
+ seedOverride: options.seed
379
+ }) : 0;
380
+ if (node.perspective.kind === "spectator") {
381
+ return {
382
+ schemaVersion: 1,
383
+ mode: "transitions",
384
+ scenario: options.identity,
385
+ perspective: node.perspective,
386
+ node,
387
+ candidates: [],
388
+ omissions: [],
389
+ page: {
390
+ limit,
391
+ evaluated: 0,
392
+ truncated: false,
393
+ nextCursor: null
394
+ }
395
+ };
396
+ }
397
+ const accepted = [];
398
+ const omissions = [];
399
+ let evaluated = 0;
400
+ for (const action of node.actions) {
401
+ const remaining = maxEvaluations - evaluated;
402
+ if (remaining <= 0) {
403
+ omissions.push({
404
+ actor: action.actor,
405
+ interactionId: action.interactionId,
406
+ code: "INPUT_DOMAIN_BUDGET"
407
+ });
408
+ continue;
409
+ }
410
+ const enumeration = enumerateScenarioInteractionParams({
411
+ replay,
412
+ seat: action.actor.seat,
413
+ interactionId: action.interactionId,
414
+ maxEvaluations: remaining
415
+ });
416
+ if (!enumeration.found || !enumeration.visible || !enumeration.enumeration) {
417
+ continue;
418
+ }
419
+ evaluated += enumeration.enumeration.evaluated;
420
+ if (enumeration.enumeration.status !== "enumerated") {
421
+ omissions.push({
422
+ actor: action.actor,
423
+ interactionId: action.interactionId,
424
+ code: enumeration.enumeration.status === "budget" ? "INPUT_DOMAIN_BUDGET" : "INPUT_DOMAIN_NOT_ENUMERABLE",
425
+ ...enumeration.enumeration.inputKey ? { inputKey: enumeration.enumeration.inputKey } : {}
426
+ });
427
+ continue;
428
+ }
429
+ const assignments = [...enumeration.enumeration.assignments].sort(
430
+ compareCanonicalScenarioJson
431
+ );
432
+ for (const assignment of assignments) {
433
+ const command = {
434
+ actor: { seat: action.actor.seat },
435
+ interactionId: action.interactionId,
436
+ params: projectScenarioCommandParams({
437
+ game: options.game,
438
+ phase: node.flow.phase,
439
+ interactionId: action.interactionId,
440
+ params: assignment,
441
+ playerIds: node.setup.players ? Array.from(
442
+ { length: node.setup.players },
443
+ (_, seat) => options.game.contract.manifest.literals.playerIds[seat] ?? `player-${seat + 1}`
444
+ ) : [],
445
+ path: `explore.${action.interactionId}`
446
+ })
447
+ };
448
+ const advanced = await advanceScenarioReplay({
449
+ replay,
450
+ command
451
+ });
452
+ if (advanced.kind !== "accepted") continue;
453
+ const afterNode = inspectScenarioReplayNode({
454
+ replay: advanced.replay,
455
+ perspective: options.perspective
456
+ });
457
+ accepted.push({
458
+ ordinal: accepted.length,
459
+ command,
460
+ after: {
461
+ checkpointDigest: afterNode.checkpointDigest,
462
+ flow: afterNode.flow,
463
+ publicStateDigest: digestScenarioJson(
464
+ afterNode.publicState
465
+ ),
466
+ viewDigest: digestScenarioJson(afterNode.view),
467
+ actions: afterNode.actions.map(({ actor, interactionId }) => ({
468
+ actor,
469
+ interactionId
470
+ })),
471
+ entropy: afterNode.entropy,
472
+ dispatchTrace: afterNode.dispatchTrace
473
+ }
474
+ });
475
+ }
476
+ }
477
+ const candidates = accepted.slice(startOrdinal, startOrdinal + limit);
478
+ const nextOrdinal = startOrdinal + candidates.length;
479
+ const truncated = nextOrdinal < accepted.length;
480
+ return {
481
+ schemaVersion: 1,
482
+ mode: "transitions",
483
+ scenario: structuredClone(options.identity),
484
+ perspective: node.perspective,
485
+ node,
486
+ candidates,
487
+ omissions,
488
+ page: {
489
+ limit,
490
+ evaluated,
491
+ truncated,
492
+ nextCursor: truncated ? createExploreCursor({
493
+ scenario: options.identity,
494
+ checkpointDigest: node.checkpointDigest,
495
+ perspective: node.perspective,
496
+ seedOverride: options.seed,
497
+ nextOrdinal
498
+ }) : null
499
+ }
500
+ };
501
+ }
502
+ async function exploreSeeds(options, range) {
503
+ assertSeedRange(range);
504
+ const checkpoint = defaultCheckpoint(options.scenario, options.at);
505
+ const variants = [];
506
+ for (let seed = range.start; seed <= range.end; seed += 1) {
507
+ try {
508
+ const inspected = await inspectScenario({
509
+ game: options.game,
510
+ scenario: options.scenario,
511
+ identity: options.identity,
512
+ perspective: options.perspective,
513
+ at: checkpoint,
514
+ seed
515
+ });
516
+ const replay = await replayScenario({
517
+ game: options.game,
518
+ scenario: scenarioWithSeed(options.scenario, seed),
519
+ at: checkpoint
520
+ });
521
+ const counts = await concreteOptionCounts({
522
+ replay,
523
+ node: inspected.node,
524
+ maxEvaluations: options.maxEvaluations ?? DEFAULT_EXPLORE_EVALUATIONS
525
+ });
526
+ variants.push({
527
+ seed,
528
+ status: "replayed",
529
+ checkpointDigest: inspected.node.checkpointDigest,
530
+ entropy: inspected.node.entropy,
531
+ observable: {
532
+ publicState: inspected.node.publicState,
533
+ view: inspected.node.view
534
+ },
535
+ signature: {
536
+ phase: inspected.node.flow.phase,
537
+ step: inspected.node.flow.step,
538
+ actions: inspected.node.actions.map(({ actor, interactionId }) => ({
539
+ seat: actor.seat,
540
+ interactionId,
541
+ concreteOptionCount: counts.get(`${actor.seat}:${interactionId}`) ?? "lazy"
542
+ }))
543
+ }
544
+ });
545
+ } catch (error) {
546
+ if (!(error instanceof ScenarioReplayError)) throw error;
547
+ variants.push({
548
+ seed,
549
+ status: "rejected",
550
+ rejection: {
551
+ segment: error.segment,
552
+ sourceIndex: error.index,
553
+ interactionId: error.interactionId,
554
+ errorCode: error.errorCode
555
+ }
556
+ });
557
+ }
558
+ }
559
+ return {
560
+ schemaVersion: 1,
561
+ mode: "seeds",
562
+ scenario: structuredClone(options.identity),
563
+ perspective: (await inspectSeedPerspective(options)).node.perspective,
564
+ checkpoint,
565
+ variants
566
+ };
567
+ }
568
+ async function inspectSeedPerspective(options) {
569
+ return inspectScenario({
570
+ game: options.game,
571
+ scenario: options.scenario,
572
+ identity: options.identity,
573
+ perspective: options.perspective,
574
+ at: { segment: "setup", completed: 0 }
575
+ });
576
+ }
577
+ async function concreteOptionCounts(options) {
578
+ assertTransitionLimits(DEFAULT_EXPLORE_LIMIT, options.maxEvaluations);
579
+ const counts = /* @__PURE__ */ new Map();
580
+ let evaluated = 0;
581
+ for (const action of options.node.actions) {
582
+ const remaining = options.maxEvaluations - evaluated;
583
+ if (remaining <= 0) {
584
+ counts.set(`${action.actor.seat}:${action.interactionId}`, "lazy");
585
+ continue;
586
+ }
587
+ const result = enumerateScenarioInteractionParams({
588
+ replay: options.replay,
589
+ seat: action.actor.seat,
590
+ interactionId: action.interactionId,
591
+ maxEvaluations: remaining
592
+ });
593
+ if (!result.found || !result.visible || !result.enumeration) continue;
594
+ evaluated += result.enumeration.evaluated;
595
+ counts.set(
596
+ `${action.actor.seat}:${action.interactionId}`,
597
+ result.enumeration.status === "enumerated" ? result.enumeration.assignments.length : "lazy"
598
+ );
599
+ }
600
+ return counts;
601
+ }
602
+ function scenarioWithSeed(scenario, seed) {
603
+ return seed === void 0 ? scenario : { ...scenario, setup: { ...scenario.setup, seed } };
604
+ }
605
+ function defaultCheckpoint(scenario, at) {
606
+ return at ?? {
607
+ segment: "given",
608
+ completed: scenario.given.length
609
+ };
610
+ }
611
+ function assertTransitionLimits(limit, maxEvaluations) {
612
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_EXPLORE_LIMIT) {
613
+ throw new ScenarioExploreError({
614
+ code: "TEST_EXPLORE_LIMIT_INVALID",
615
+ message: `Explore limit must be between 1 and ${MAX_EXPLORE_LIMIT}.`,
616
+ context: { requested: limit, minimum: 1, maximum: MAX_EXPLORE_LIMIT }
617
+ });
618
+ }
619
+ if (!Number.isSafeInteger(maxEvaluations) || maxEvaluations < 1 || maxEvaluations > MAX_EXPLORE_EVALUATIONS) {
620
+ throw new ScenarioExploreError({
621
+ code: "TEST_EXPLORE_LIMIT_INVALID",
622
+ message: `Explore max evaluations must be between 1 and ${MAX_EXPLORE_EVALUATIONS}.`,
623
+ context: {
624
+ requested: maxEvaluations,
625
+ minimum: 1,
626
+ maximum: MAX_EXPLORE_EVALUATIONS
627
+ }
628
+ });
629
+ }
630
+ }
631
+ function assertSeedRange(range) {
632
+ const width = range.end - range.start + 1;
633
+ if (!Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end) || range.start > range.end || !Number.isSafeInteger(width) || width < 1 || width > MAX_EXPLORE_SEEDS) {
634
+ throw new ScenarioExploreError({
635
+ code: "TEST_SEED_RANGE_INVALID",
636
+ message: `Seed range must contain 1 through ${MAX_EXPLORE_SEEDS} inclusive safe-integer seeds.`,
637
+ context: {
638
+ start: range.start,
639
+ end: range.end,
640
+ maximumWidth: MAX_EXPLORE_SEEDS
641
+ }
642
+ });
643
+ }
39
644
  }
40
645
 
41
646
  // src/testing/ui-fixture/canonical.ts
42
647
  import { createHash } from "crypto";
43
648
  var sha256DigestPattern = /^sha256:[a-f0-9]{64}$/;
649
+ function compareCanonicalStrings(left, right) {
650
+ return left === right ? 0 : left < right ? -1 : 1;
651
+ }
44
652
  function isSha256Digest(value) {
45
653
  return sha256DigestPattern.test(value);
46
654
  }
@@ -66,7 +674,7 @@ function canonicalizeUIFixtureJson(value) {
66
674
  }
67
675
  const entries = Object.entries(value);
68
676
  return Object.fromEntries(
69
- entries.filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalizeUIFixtureJson(item)])
677
+ entries.filter(([, item]) => item !== void 0).sort(([left], [right]) => compareCanonicalStrings(left, right)).map(([key, item]) => [key, canonicalizeUIFixtureJson(item)])
70
678
  );
71
679
  }
72
680
  }
@@ -1357,6 +1965,9 @@ function assertStep(stepId, expect) {
1357
1965
  }
1358
1966
  export {
1359
1967
  FixturePluginRuntime,
1968
+ ScenarioAssertionError,
1969
+ ScenarioDefinitionValidationError,
1970
+ ScenarioReplayError,
1360
1971
  StaleContractArtifactError,
1361
1972
  UI_SCENARIO_FIXTURE_BUNDLE_SCHEMA_VERSION,
1362
1973
  UI_SCENARIO_FIXTURE_PLUGIN_RUNTIME_PROTOCOL,
@@ -1364,6 +1975,7 @@ export {
1364
1975
  UI_SCENARIO_FIXTURE_SUPPORTED_BROWSER_PROTOCOL_MAJOR,
1365
1976
  activate,
1366
1977
  assertDeterministicUIScenarioFixture,
1978
+ assertScenario,
1367
1979
  assertStep,
1368
1980
  assertSupportedBrowserInteractionProtocol,
1369
1981
  assertUIStepExpectationSatisfied,
@@ -1378,11 +1990,9 @@ export {
1378
1990
  createExpectApi,
1379
1991
  createFixtureHostHarness,
1380
1992
  createReducerScenarioRunner,
1381
- createTestRuntime,
1993
+ createScenarioAuthoring,
1382
1994
  createUIParityObservationFromFixture,
1383
1995
  defaultFixtureEnvironmentInit,
1384
- defineBase,
1385
- defineScenario,
1386
1996
  defineUIScenario,
1387
1997
  digestPluginProtocolTape,
1388
1998
  digestUIFixtureJson,
@@ -1390,8 +2000,10 @@ export {
1390
2000
  digestUIFixtureTransportRequest,
1391
2001
  digestUIScenarioFixture,
1392
2002
  drag,
2003
+ exploreScenario,
1393
2004
  fill,
1394
2005
  fixtureEnvironmentInitFor,
2006
+ inspectScenario,
1395
2007
  isSha256Digest,
1396
2008
  isStaleContractArtifactError,
1397
2009
  parseUIParityObservationV1,
@@ -1400,8 +2012,11 @@ export {
1400
2012
  pluginProtocolTapeSchema,
1401
2013
  portableSemanticReplayStepSchema,
1402
2014
  press,
2015
+ probeScenarioCommand,
2016
+ replayScenario,
1403
2017
  serializeUIScenarioFixture,
1404
2018
  submit,
2019
+ toScenarioReplayDefinition,
1405
2020
  uiFixtureFrameSchema,
1406
2021
  uiFixtureProtocolStepSchema,
1407
2022
  uiReplayExecutionSchema,