@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.
- package/REFERENCE.md +1292 -287
- package/dist/authoring/index.d.ts +4 -9
- package/dist/authoring/index.js +11 -547
- package/dist/authoring/index.js.map +1 -1
- package/dist/authoring-compiler.d.ts +27 -0
- package/dist/authoring-compiler.js +12 -0
- package/dist/authoring-compiler.js.map +1 -0
- package/dist/authoring-materialize-cli.d.ts +1 -0
- package/dist/authoring-materialize-cli.js +46 -0
- package/dist/authoring-materialize-cli.js.map +1 -0
- package/dist/browser-interaction.d.ts +1 -1
- package/dist/{chunk-TGMXBRKY.js → chunk-3PFDHTR7.js} +19 -7
- package/dist/chunk-3PFDHTR7.js.map +1 -0
- package/dist/chunk-45P5CEGH.js +176 -0
- package/dist/chunk-45P5CEGH.js.map +1 -0
- package/dist/chunk-5XTAQ4EQ.js +32 -0
- package/dist/chunk-5XTAQ4EQ.js.map +1 -0
- package/dist/chunk-7ADPYKFT.js +532 -0
- package/dist/chunk-7ADPYKFT.js.map +1 -0
- package/dist/{chunk-RKGJ64UN.js → chunk-BUOODBVO.js} +2 -2
- package/dist/{chunk-VLG4YST5.js → chunk-GMOBG7Y7.js} +4 -4
- package/dist/{chunk-Q322XCY2.js → chunk-JRGP6W4V.js} +3 -3
- package/dist/chunk-L3W2FPRQ.js +1793 -0
- package/dist/chunk-L3W2FPRQ.js.map +1 -0
- package/dist/{chunk-3QIZKXJ2.js → chunk-NGYFBNBP.js} +3 -3
- package/dist/chunk-P6IJ5YVY.js +154 -0
- package/dist/chunk-P6IJ5YVY.js.map +1 -0
- package/dist/{chunk-5IHU5CUK.js → chunk-QQNL4WTX.js} +25 -22
- package/dist/chunk-QQNL4WTX.js.map +1 -0
- package/dist/{chunk-52DH7VQF.js → chunk-SACZWUA6.js} +143 -47
- package/dist/chunk-SACZWUA6.js.map +1 -0
- package/dist/{chunk-KYEWGZ2Y.js → chunk-SFOAY5H4.js} +2 -2
- package/dist/{chunk-KYEWGZ2Y.js.map → chunk-SFOAY5H4.js.map} +1 -1
- package/dist/chunk-UNVCT5Z7.js +73 -0
- package/dist/chunk-UNVCT5Z7.js.map +1 -0
- package/dist/{chunk-NO537WSS.js → chunk-WDR7SKBM.js} +2 -2
- package/dist/chunk-XDGRC6OR.js +7039 -0
- package/dist/chunk-XDGRC6OR.js.map +1 -0
- package/dist/chunk-XLHLFTLI.js +399 -0
- package/dist/chunk-XLHLFTLI.js.map +1 -0
- package/dist/{chunk-B2YBS3SG.js → chunk-YNW3KBDE.js} +41 -13
- package/dist/chunk-YNW3KBDE.js.map +1 -0
- package/dist/codegen.js +1 -1
- package/dist/{components-BTwSIjXj.d.ts → components-BPzsSRs8.d.ts} +1 -1
- package/dist/definitions-0kjPV9wh.d.ts +294 -0
- package/dist/{diagnostics-1BWjRo6-.d.ts → diagnostics-DIi3ina7.d.ts} +2 -1
- package/dist/{index-CFTjJIXd.d.ts → index-CWzWpQzM.d.ts} +1 -1
- package/dist/{index.d-DMASeF6M.d.ts → index.d-C7Ztirg6.d.ts} +234 -5
- package/dist/index.js +2 -2
- package/dist/package-set.d.ts +2 -2
- package/dist/package-set.js +2 -2
- package/dist/{protocol-835hEGne.d.ts → protocol-B2LWcqB4.d.ts} +14 -0
- package/dist/reducer/advanced.d.ts +4 -4
- package/dist/reducer/advanced.js +7 -26
- package/dist/reducer/advanced.js.map +1 -1
- package/dist/reducer-contract.d.ts +1 -1
- package/dist/reducer-contract.js +2 -2
- package/dist/reducer.d.ts +37 -28
- package/dist/reducer.js +43 -6018
- package/dist/reducer.js.map +1 -1
- package/dist/reference-game-compiler.d.ts +45 -0
- package/dist/reference-game-compiler.js +25 -0
- package/dist/reference-game-compiler.js.map +1 -0
- package/dist/reference-game-source-manifest-cli.d.ts +1 -0
- package/dist/reference-game-source-manifest-cli.js +84 -0
- package/dist/reference-game-source-manifest-cli.js.map +1 -0
- package/dist/reference-games/index.d.ts +7 -217
- package/dist/reference-games/index.js +28 -208
- package/dist/reference-games/index.js.map +1 -1
- package/dist/runtime/primitives.d.ts +3 -3
- package/dist/runtime/primitives.js +3 -3
- package/dist/runtime/runtime-api.d.ts +1 -1
- package/dist/runtime/workspace-contract.d.ts +4 -4
- package/dist/runtime/workspace-contract.js +4 -4
- package/dist/{runtime-Dkfm1x2r.d.ts → runtime-B_w9AO2n.d.ts} +60 -5
- package/dist/{runtime-api-C6aWo9c0.d.ts → runtime-api-xCWLMQRT.d.ts} +2 -0
- package/dist/runtime.d.ts +7 -7
- package/dist/runtime.js +13 -13
- package/dist/schema-9jZzlEWZ.d.ts +220 -0
- package/dist/{stale-contract-artifact-error-C5AaZPJ8.d.ts → stale-contract-artifact-error-XLaweZtF.d.ts} +1 -1
- package/dist/testing-compiler.d.ts +36 -0
- package/dist/testing-compiler.js +236 -0
- package/dist/testing-compiler.js.map +1 -0
- package/dist/testing-runtime.d.ts +97 -21
- package/dist/testing-runtime.js +154 -95
- package/dist/testing-runtime.js.map +1 -1
- package/dist/testing.d.ts +178 -60
- package/dist/testing.js +641 -26
- package/dist/testing.js.map +1 -1
- package/dist/types-Bvptkukh.d.ts +122 -0
- package/dist/{types-aI4V9CWe.d.ts → types-_zfC_7CD.d.ts} +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/ui/components.d.ts +2 -2
- package/dist/ui/plugin-styles.css +1 -1
- package/dist/{ui-contract-Ue6SywvO.d.ts → ui-contract-CUC7Lyoc.d.ts} +1 -1
- package/dist/ui.d.ts +3 -3
- package/dist/{views-32rSsfeU.d.ts → views-DE2eZxPz.d.ts} +42 -13
- package/package.json +22 -3
- package/dist/chunk-3OZMHZK3.js +0 -40
- package/dist/chunk-3OZMHZK3.js.map +0 -1
- package/dist/chunk-52DH7VQF.js.map +0 -1
- package/dist/chunk-5IHU5CUK.js.map +0 -1
- package/dist/chunk-B2YBS3SG.js.map +0 -1
- package/dist/chunk-IU6KGPY7.js +0 -659
- package/dist/chunk-IU6KGPY7.js.map +0 -1
- package/dist/chunk-TGMXBRKY.js.map +0 -1
- package/dist/types-DcADVHe9.d.ts +0 -192
- /package/dist/{chunk-RKGJ64UN.js.map → chunk-BUOODBVO.js.map} +0 -0
- /package/dist/{chunk-VLG4YST5.js.map → chunk-GMOBG7Y7.js.map} +0 -0
- /package/dist/{chunk-Q322XCY2.js.map → chunk-JRGP6W4V.js.map} +0 -0
- /package/dist/{chunk-3QIZKXJ2.js.map → chunk-NGYFBNBP.js.map} +0 -0
- /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-
|
|
5
|
-
import "./chunk-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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-
|
|
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/
|
|
34
|
-
function
|
|
35
|
-
|
|
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
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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,
|