@storylet-studio/runtime 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +100 -1019
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +30 -942
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -23,941 +23,21 @@ __export(index_exports, {
|
|
|
23
23
|
Engine: () => Engine,
|
|
24
24
|
Flow: () => Flow,
|
|
25
25
|
describeBundle: () => describeBundle,
|
|
26
|
-
makePrng: () => makePrng,
|
|
27
|
-
shuffleInPlace: () => shuffleInPlace
|
|
26
|
+
makePrng: () => import_expr.makePrng,
|
|
27
|
+
shuffleInPlace: () => import_expr.shuffleInPlace
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
case "s":
|
|
39
|
-
return { kind: "string", value: node[1] };
|
|
40
|
-
case "sv":
|
|
41
|
-
return { kind: "scopedvar", scope: node[1], name: node[2] };
|
|
42
|
-
case "u":
|
|
43
|
-
return { kind: "unary", op: node[1], operand: deserialiseAst(node[2]) };
|
|
44
|
-
case "bin":
|
|
45
|
-
return { kind: "binary", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };
|
|
46
|
-
case "call": {
|
|
47
|
-
const args = node.slice(2).map(deserialiseAst);
|
|
48
|
-
return { kind: "call", name: node[1], args };
|
|
49
|
-
}
|
|
50
|
-
case "fd":
|
|
51
|
-
return { kind: "flagdelta", sign: node[1], name: node[2] };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ../../../expr/packages/expr/src/evaluate.ts
|
|
56
|
-
var EvalError = class extends Error {
|
|
57
|
-
constructor(message) {
|
|
58
|
-
super(message);
|
|
59
|
-
this.name = "EvalError";
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
function evaluate(node, ctx, dialect) {
|
|
63
|
-
const missingPolicy = new Map(
|
|
64
|
-
dialect.scopes.map((s) => [s.token, s.missing ?? "false"])
|
|
65
|
-
);
|
|
66
|
-
const rec = (n) => {
|
|
67
|
-
switch (n.kind) {
|
|
68
|
-
case "bool":
|
|
69
|
-
return n.value;
|
|
70
|
-
case "number":
|
|
71
|
-
return n.value;
|
|
72
|
-
case "string":
|
|
73
|
-
return n.value;
|
|
74
|
-
case "scopedvar": {
|
|
75
|
-
const scope = ctx.scopes[n.scope];
|
|
76
|
-
if (scope === void 0) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
const val = typeof scope.get === "function" ? scope.get(n.name) : scope[n.name];
|
|
80
|
-
if (val === void 0) {
|
|
81
|
-
if (missingPolicy.get(n.scope) === "throw") {
|
|
82
|
-
throw new EvalError(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return val;
|
|
87
|
-
}
|
|
88
|
-
case "call": {
|
|
89
|
-
if (n.name === "advance" && !dialect.functions[n.name]) {
|
|
90
|
-
const arg = n.args[0];
|
|
91
|
-
if (n.args.length !== 1 || arg === void 0) {
|
|
92
|
-
throw new EvalError(`advance() takes exactly 1 argument, got ${n.args.length}`);
|
|
93
|
-
}
|
|
94
|
-
const ladder = ladderOf(arg, ctx);
|
|
95
|
-
if (ladder === void 0) {
|
|
96
|
-
throw new EvalError("advance() needs a quality reference (@scope.name of a quality property)");
|
|
97
|
-
}
|
|
98
|
-
const current = stageIndex(rec(arg), ladder, "advance");
|
|
99
|
-
return ladder[Math.min(current + 1, ladder.length - 1)];
|
|
100
|
-
}
|
|
101
|
-
const def = dialect.functions[n.name];
|
|
102
|
-
if (!def) throw new EvalError(`unknown function '${n.name}'`);
|
|
103
|
-
return def.eval(n.args, { evaluate: rec, ctx });
|
|
104
|
-
}
|
|
105
|
-
case "flagdelta":
|
|
106
|
-
throw new EvalError("flagdelta node is only valid as an argument to a flag-delta function");
|
|
107
|
-
case "unary": {
|
|
108
|
-
if (n.op === "not") {
|
|
109
|
-
const val2 = rec(n.operand);
|
|
110
|
-
if (typeof val2 !== "boolean") throw new EvalError(`'not' requires a boolean operand, got ${typeof val2}`);
|
|
111
|
-
return !val2;
|
|
112
|
-
}
|
|
113
|
-
const val = rec(n.operand);
|
|
114
|
-
if (typeof val !== "number") throw new EvalError(`unary '-' requires a numeric operand, got ${typeof val}`);
|
|
115
|
-
return -val;
|
|
116
|
-
}
|
|
117
|
-
case "binary": {
|
|
118
|
-
if (n.op === "and") {
|
|
119
|
-
const l = rec(n.left);
|
|
120
|
-
if (typeof l !== "boolean") throw new EvalError(`'and' requires boolean operands, left is ${typeof l}`);
|
|
121
|
-
if (!l) return false;
|
|
122
|
-
const r = rec(n.right);
|
|
123
|
-
if (typeof r !== "boolean") throw new EvalError(`'and' requires boolean operands, right is ${typeof r}`);
|
|
124
|
-
return r;
|
|
125
|
-
}
|
|
126
|
-
if (n.op === "or") {
|
|
127
|
-
const l = rec(n.left);
|
|
128
|
-
if (typeof l !== "boolean") throw new EvalError(`'or' requires boolean operands, left is ${typeof l}`);
|
|
129
|
-
if (l) return true;
|
|
130
|
-
const r = rec(n.right);
|
|
131
|
-
if (typeof r !== "boolean") throw new EvalError(`'or' requires boolean operands, right is ${typeof r}`);
|
|
132
|
-
return r;
|
|
133
|
-
}
|
|
134
|
-
const left = rec(n.left);
|
|
135
|
-
const right = rec(n.right);
|
|
136
|
-
const lLadder = ladderOf(n.left, ctx);
|
|
137
|
-
const rLadder = ladderOf(n.right, ctx);
|
|
138
|
-
const ladder = lLadder ?? rLadder;
|
|
139
|
-
if (ladder !== void 0) {
|
|
140
|
-
if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {
|
|
141
|
-
if (n.op === ">" || n.op === ">=" || n.op === "<" || n.op === "<=") {
|
|
142
|
-
throw new EvalError(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
switch (n.op) {
|
|
146
|
-
case ">":
|
|
147
|
-
return stageIndex(left, ladder, ">") > stageIndex(right, ladder, ">");
|
|
148
|
-
case ">=":
|
|
149
|
-
return stageIndex(left, ladder, ">=") >= stageIndex(right, ladder, ">=");
|
|
150
|
-
case "<":
|
|
151
|
-
return stageIndex(left, ladder, "<") < stageIndex(right, ladder, "<");
|
|
152
|
-
case "<=":
|
|
153
|
-
return stageIndex(left, ladder, "<=") <= stageIndex(right, ladder, "<=");
|
|
154
|
-
case "+":
|
|
155
|
-
case "-":
|
|
156
|
-
case "*":
|
|
157
|
-
case "/":
|
|
158
|
-
throw new EvalError(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);
|
|
159
|
-
default:
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
switch (n.op) {
|
|
164
|
-
case "==":
|
|
165
|
-
return valueEquals(left, right);
|
|
166
|
-
case "!=":
|
|
167
|
-
return !valueEquals(left, right);
|
|
168
|
-
case ">":
|
|
169
|
-
assertNumbers(left, right, ">");
|
|
170
|
-
return left > right;
|
|
171
|
-
case ">=":
|
|
172
|
-
assertNumbers(left, right, ">=");
|
|
173
|
-
return left >= right;
|
|
174
|
-
case "<":
|
|
175
|
-
assertNumbers(left, right, "<");
|
|
176
|
-
return left < right;
|
|
177
|
-
case "<=":
|
|
178
|
-
assertNumbers(left, right, "<=");
|
|
179
|
-
return left <= right;
|
|
180
|
-
case "+":
|
|
181
|
-
if (typeof left === "number" && typeof right === "number") return left + right;
|
|
182
|
-
if (typeof left === "string" && typeof right === "string") return left + right;
|
|
183
|
-
throw new EvalError(`'+' requires two numbers or two strings, got ${typeof left} and ${typeof right}`);
|
|
184
|
-
case "-":
|
|
185
|
-
assertNumbers(left, right, "-");
|
|
186
|
-
return left - right;
|
|
187
|
-
case "*":
|
|
188
|
-
assertNumbers(left, right, "*");
|
|
189
|
-
return left * right;
|
|
190
|
-
case "/":
|
|
191
|
-
assertNumbers(left, right, "/");
|
|
192
|
-
if (right === 0) throw new EvalError("division by zero");
|
|
193
|
-
return left / right;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
return rec(node);
|
|
199
|
-
}
|
|
200
|
-
function valueEquals(a, b) {
|
|
201
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
202
|
-
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
203
|
-
if (a.length !== b.length) return false;
|
|
204
|
-
const x = [...a].sort();
|
|
205
|
-
const y = [...b].sort();
|
|
206
|
-
for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
return a === b;
|
|
210
|
-
}
|
|
211
|
-
function assertNumbers(l, r, op) {
|
|
212
|
-
if (typeof l !== "number" || typeof r !== "number") {
|
|
213
|
-
throw new EvalError(`'${op}' requires numeric operands, got ${typeof l} and ${typeof r}`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
function ladderOf(node, ctx) {
|
|
217
|
-
if (node.kind !== "scopedvar" || ctx.qualities === void 0) return void 0;
|
|
218
|
-
return ctx.qualities(node.scope, node.name);
|
|
219
|
-
}
|
|
220
|
-
function stageIndex(value, ladder, op) {
|
|
221
|
-
if (typeof value !== "string") {
|
|
222
|
-
throw new EvalError(`'${op}' on a quality compares stages, got ${typeof value}`);
|
|
223
|
-
}
|
|
224
|
-
const i = ladder.indexOf(value);
|
|
225
|
-
if (i < 0) throw new EvalError(`"${value}" is not a stage of this quality (stages: ${ladder.join(", ")})`);
|
|
226
|
-
return i;
|
|
227
|
-
}
|
|
228
|
-
var sameLadder = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
229
|
-
|
|
230
|
-
// ../../../expr/packages/expr/src/prng.ts
|
|
231
|
-
function toUint32(seed) {
|
|
232
|
-
if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;
|
|
233
|
-
const modded = Math.trunc(seed) % 4294967296;
|
|
234
|
-
return modded < 0 ? modded + 4294967296 : modded;
|
|
235
|
-
}
|
|
236
|
-
function makePrng(seed) {
|
|
237
|
-
let s = toUint32(seed);
|
|
238
|
-
return {
|
|
239
|
-
next() {
|
|
240
|
-
s = s + 1831565813 >>> 0;
|
|
241
|
-
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
242
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
243
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
244
|
-
},
|
|
245
|
-
state() {
|
|
246
|
-
return s;
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
function shuffleInPlace(arr, prng) {
|
|
251
|
-
for (let i = arr.length - 1; i > 0; i--) {
|
|
252
|
-
const j = Math.floor(prng.next() * (i + 1));
|
|
253
|
-
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ../../../expr/packages/expr-specificity/src/index.ts
|
|
258
|
-
var CHECK_FLAGS_COUNTING_CALL = {
|
|
259
|
-
name: "check_flags",
|
|
260
|
-
count: (node) => Math.max(1, node.args.length - 1)
|
|
261
|
-
};
|
|
262
|
-
var DEFAULT_COUNTING_CALLS = [CHECK_FLAGS_COUNTING_CALL];
|
|
263
|
-
function matchedSpecificity(node, evalTruthy, opts) {
|
|
264
|
-
const countingCalls = opts?.countingCalls ?? DEFAULT_COUNTING_CALLS;
|
|
265
|
-
return walk(node, opts?.want ?? true, evalTruthy, countingCalls);
|
|
266
|
-
}
|
|
267
|
-
function walk(node, want, evalTruthy, countingCalls) {
|
|
268
|
-
if (node.kind === "binary" && (node.op === "and" || node.op === "or")) {
|
|
269
|
-
const l = walk(node.left, want, evalTruthy, countingCalls);
|
|
270
|
-
const r = walk(node.right, want, evalTruthy, countingCalls);
|
|
271
|
-
const behaveAsAnd = node.op === "and" === want;
|
|
272
|
-
if (behaveAsAnd) return l > 0 && r > 0 ? l + r : 0;
|
|
273
|
-
return Math.max(l, r);
|
|
274
|
-
}
|
|
275
|
-
if (node.kind === "unary" && node.op === "not") {
|
|
276
|
-
return walk(node.operand, !want, evalTruthy, countingCalls);
|
|
277
|
-
}
|
|
278
|
-
if (node.kind === "call") {
|
|
279
|
-
const rule = countingCalls.find((c) => c.name === node.name);
|
|
280
|
-
if (rule) {
|
|
281
|
-
const operands = rule.count(node);
|
|
282
|
-
const holds = evalTruthy(node);
|
|
283
|
-
if (want) return holds ? operands : 0;
|
|
284
|
-
return holds ? 0 : 1;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return evalTruthy(node) === want ? 1 : 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ../dialect/src/engine-scopes.ts
|
|
291
|
-
var ENGINE_SCOPES = [
|
|
292
|
-
{ token: "patter", engine: "Patterplay", means: "Patter's shared globals" },
|
|
293
|
-
{ token: "story", engine: "Storylet Engine", means: "the Storylet Engine's shared @story properties" }
|
|
294
|
-
];
|
|
295
|
-
|
|
296
|
-
// ../dialect/src/index.ts
|
|
297
|
-
var NEVER_PLAYED = 9999;
|
|
298
|
-
var host = (h) => h.ctx.host ?? {};
|
|
299
|
-
var stringArg = (fn, args, h, i) => {
|
|
300
|
-
const v = h.evaluate(args[i]);
|
|
301
|
-
if (typeof v !== "string" || v === "") {
|
|
302
|
-
throw new EvalError(`${fn}() argument ${i + 1} must be a non-empty string`);
|
|
303
|
-
}
|
|
304
|
-
return v;
|
|
305
|
-
};
|
|
306
|
-
var flagsArg = (fn, args, h) => {
|
|
307
|
-
if (args.length === 0) {
|
|
308
|
-
throw new EvalError(`${fn}() requires at least one argument (the flags property)`);
|
|
309
|
-
}
|
|
310
|
-
const v = h.evaluate(args[0]);
|
|
311
|
-
if (Array.isArray(v)) return v;
|
|
312
|
-
if (v === false) return [];
|
|
313
|
-
throw new EvalError(`${fn}() first argument must be a flags property`);
|
|
314
|
-
};
|
|
315
|
-
var OWN_SCOPES = ["story", "world", "box", "deck", "hand"];
|
|
316
|
-
var EXTERNAL_SCOPES = ENGINE_SCOPES.map((s) => s.token).filter((t) => !OWN_SCOPES.includes(t));
|
|
317
|
-
var storyletsDialect = {
|
|
318
|
-
// A missing property in a PRESENT scope is always an error: every property
|
|
319
|
-
// is declared with a default, so absence means a publish bug, a drifted
|
|
320
|
-
// save, or a foreign scope the host never fed (schema 6.2). The same holds
|
|
321
|
-
// for another engine's scope: `@patter.glod` is a typo, and it says so the
|
|
322
|
-
// first time the card is evaluated rather than quietly reading false.
|
|
323
|
-
scopes: [
|
|
324
|
-
...OWN_SCOPES.map((token) => ({ token, missing: "throw" })),
|
|
325
|
-
...EXTERNAL_SCOPES.map((token) => ({ token, missing: "throw" }))
|
|
326
|
-
],
|
|
327
|
-
defaultScope: "story",
|
|
328
|
-
functions: {
|
|
329
|
-
random: {
|
|
330
|
-
minArgs: 2,
|
|
331
|
-
maxArgs: 2,
|
|
332
|
-
returnType: "number",
|
|
333
|
-
eval(args, h) {
|
|
334
|
-
if (args.length !== 2) throw new EvalError("random(a, b) requires exactly 2 arguments");
|
|
335
|
-
const nextRandom = host(h).nextRandom;
|
|
336
|
-
if (!nextRandom) throw new EvalError("random() called without a PRNG in context");
|
|
337
|
-
const a = h.evaluate(args[0]);
|
|
338
|
-
const b = h.evaluate(args[1]);
|
|
339
|
-
if (typeof a !== "number" || typeof b !== "number") {
|
|
340
|
-
throw new EvalError("random(a, b) arguments must be numbers");
|
|
341
|
-
}
|
|
342
|
-
if (!Number.isInteger(a) || !Number.isInteger(b)) {
|
|
343
|
-
throw new EvalError("random(a, b) arguments must be integers");
|
|
344
|
-
}
|
|
345
|
-
const lo = Math.min(a, b);
|
|
346
|
-
const hi = Math.max(a, b);
|
|
347
|
-
return Math.floor(nextRandom() * (hi - lo + 1)) + lo;
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
check_flags: {
|
|
351
|
-
minArgs: 1,
|
|
352
|
-
returnType: "boolean",
|
|
353
|
-
flagDeltaArgs: true,
|
|
354
|
-
eval(args, h) {
|
|
355
|
-
const flags = flagsArg("check_flags", args, h);
|
|
356
|
-
for (let i = 1; i < args.length; i++) {
|
|
357
|
-
const arg = args[i];
|
|
358
|
-
if (arg.kind !== "flagdelta") {
|
|
359
|
-
throw new EvalError("check_flags() flag args must be +flagName or -flagName");
|
|
360
|
-
}
|
|
361
|
-
if (arg.sign === "+" ? !flags.includes(arg.name) : flags.includes(arg.name)) {
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
set_flags: {
|
|
369
|
-
minArgs: 1,
|
|
370
|
-
returnType: "flags",
|
|
371
|
-
flagDeltaArgs: true,
|
|
372
|
-
eval(args, h) {
|
|
373
|
-
const result = [...flagsArg("set_flags", args, h)];
|
|
374
|
-
for (let i = 1; i < args.length; i++) {
|
|
375
|
-
const arg = args[i];
|
|
376
|
-
if (arg.kind !== "flagdelta") {
|
|
377
|
-
throw new EvalError("set_flags() flag args must be +flagName or -flagName");
|
|
378
|
-
}
|
|
379
|
-
if (arg.sign === "+") {
|
|
380
|
-
if (!result.includes(arg.name)) result.push(arg.name);
|
|
381
|
-
} else {
|
|
382
|
-
const idx = result.indexOf(arg.name);
|
|
383
|
-
if (idx >= 0) result.splice(idx, 1);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return result.sort();
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
count_played: {
|
|
390
|
-
minArgs: 1,
|
|
391
|
-
maxArgs: 1,
|
|
392
|
-
returnType: "number",
|
|
393
|
-
eval(args, h) {
|
|
394
|
-
const card = stringArg("count_played", args, h, 0);
|
|
395
|
-
const fn = host(h).countPlayed;
|
|
396
|
-
if (!fn) throw new EvalError("count_played() called without a play log in context");
|
|
397
|
-
return fn(card);
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
turns_since_played: {
|
|
401
|
-
minArgs: 1,
|
|
402
|
-
maxArgs: 1,
|
|
403
|
-
returnType: "number",
|
|
404
|
-
eval(args, h) {
|
|
405
|
-
const card = stringArg("turns_since_played", args, h, 0);
|
|
406
|
-
const fn = host(h).turnsSincePlayed;
|
|
407
|
-
if (!fn) throw new EvalError("turns_since_played() called without a play log in context");
|
|
408
|
-
return fn(card);
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
count_played_in: {
|
|
412
|
-
minArgs: 2,
|
|
413
|
-
maxArgs: 2,
|
|
414
|
-
returnType: "number",
|
|
415
|
-
eval(args, h) {
|
|
416
|
-
const dimension = stringArg("count_played_in", args, h, 0);
|
|
417
|
-
const value = stringArg("count_played_in", args, h, 1);
|
|
418
|
-
const fn = host(h).countPlayedIn;
|
|
419
|
-
if (!fn) throw new EvalError("count_played_in() called without a play log in context");
|
|
420
|
-
return fn(dimension, value);
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
turns_since_played_in: {
|
|
424
|
-
minArgs: 2,
|
|
425
|
-
maxArgs: 2,
|
|
426
|
-
returnType: "number",
|
|
427
|
-
eval(args, h) {
|
|
428
|
-
const dimension = stringArg("turns_since_played_in", args, h, 0);
|
|
429
|
-
const value = stringArg("turns_since_played_in", args, h, 1);
|
|
430
|
-
const fn = host(h).turnsSincePlayedIn;
|
|
431
|
-
if (!fn) throw new EvalError("turns_since_played_in() called without a play log in context");
|
|
432
|
-
return fn(dimension, value);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
// ../model/src/index.ts
|
|
439
|
-
function gameIdify(text) {
|
|
440
|
-
return text.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
441
|
-
}
|
|
442
|
-
function effectiveGameId(entity) {
|
|
443
|
-
const pinned = entity.gameId?.trim();
|
|
444
|
-
if (pinned) return pinned;
|
|
445
|
-
const fromTitle = entity.title ? gameIdify(entity.title) : "";
|
|
446
|
-
return fromTitle || entity.id;
|
|
447
|
-
}
|
|
448
|
-
function valueAddresses(bundle) {
|
|
449
|
-
const tags = [];
|
|
450
|
-
for (const box of bundle.boxes) {
|
|
451
|
-
const boxGameId = effectiveGameId(box);
|
|
452
|
-
for (const group of box.tagGroups) {
|
|
453
|
-
for (const tag of group.tags) {
|
|
454
|
-
const gameId = effectiveGameId(tag);
|
|
455
|
-
tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
const forms = /* @__PURE__ */ new Map();
|
|
460
|
-
for (const tag of tags) {
|
|
461
|
-
const list = forms.get(tag.gameId) ?? [];
|
|
462
|
-
if (!list.includes(tag.qualified)) list.push(tag.qualified);
|
|
463
|
-
forms.set(tag.gameId, list);
|
|
464
|
-
}
|
|
465
|
-
const print = /* @__PURE__ */ new Map();
|
|
466
|
-
const accept = /* @__PURE__ */ new Map();
|
|
467
|
-
const repeated = /* @__PURE__ */ new Map();
|
|
468
|
-
for (const tag of tags) {
|
|
469
|
-
const candidates = forms.get(tag.gameId) ?? [tag.qualified];
|
|
470
|
-
const ambiguous = candidates.length > 1;
|
|
471
|
-
print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);
|
|
472
|
-
if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);
|
|
473
|
-
if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);
|
|
474
|
-
if (ambiguous) repeated.set(tag.gameId, candidates);
|
|
475
|
-
}
|
|
476
|
-
return { print, accept, repeated };
|
|
477
|
-
}
|
|
478
|
-
function ambiguousValueAddressMessage(segment, name, candidates) {
|
|
479
|
-
const forms = candidates.map((q) => `"value.${q}.${name}"`);
|
|
480
|
-
const list = forms.length <= 1 ? forms[0] ?? "" : `${forms.slice(0, -1).join(", ")} or ${forms[forms.length - 1]}`;
|
|
481
|
-
return `"value.${segment}.${name}" names a tag in ${candidates.length} boxes; write ${list}`;
|
|
482
|
-
}
|
|
483
|
-
var PLACE_GROUP = "place";
|
|
484
|
-
var HOLE_REF = /^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/;
|
|
485
|
-
var isHoleRef = (value) => value.startsWith("@");
|
|
486
|
-
var parseHoleRef = (value) => {
|
|
487
|
-
const m = HOLE_REF.exec(value);
|
|
488
|
-
return m === null ? void 0 : { scope: m[1], name: m[2] };
|
|
489
|
-
};
|
|
490
|
-
var SAVE_SCHEMA = "storylets/save@2";
|
|
491
|
-
var SAVE_SCHEMA_V1 = "storylets/save@1";
|
|
31
|
+
// src/engine.ts
|
|
32
|
+
var import_expr2 = require("@wildwinter/expr");
|
|
33
|
+
var import_expr_specificity = require("@wildwinter/expr-specificity");
|
|
34
|
+
var import_dialect = require("@storylet-studio/dialect");
|
|
35
|
+
var import_model = require("@storylet-studio/model");
|
|
36
|
+
var import_model2 = require("@storylet-studio/model");
|
|
37
|
+
var import_scoperegistry = require("@wildwinter/scoperegistry");
|
|
492
38
|
|
|
493
|
-
//
|
|
494
|
-
var
|
|
495
|
-
/** The live values record (stable identity across reseed, so an
|
|
496
|
-
* EvalContext built over it stays valid). Read-path for evaluation;
|
|
497
|
-
* writes go through `set` so the firing rule applies. */
|
|
498
|
-
values = {};
|
|
499
|
-
decls = /* @__PURE__ */ new Map();
|
|
500
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
501
|
-
auditors = /* @__PURE__ */ new Set();
|
|
502
|
-
/** Name normalisation policy: lowercase by default (the registry's
|
|
503
|
-
* long-standing contract); a product whose names are case-significant
|
|
504
|
-
* passes identity. */
|
|
505
|
-
norm;
|
|
506
|
-
/** The address prefix this bag's rows carry, separator included (`@`,
|
|
507
|
-
* `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */
|
|
508
|
-
pathPrefix;
|
|
509
|
-
constructor(declarations = [], opts) {
|
|
510
|
-
this.norm = opts?.normalise ?? ((n) => n.toLowerCase());
|
|
511
|
-
this.pathPrefix = opts?.pathPrefix ?? "";
|
|
512
|
-
this.seed(declarations);
|
|
513
|
-
}
|
|
514
|
-
seed(declarations) {
|
|
515
|
-
for (const d of declarations) {
|
|
516
|
-
const name = this.norm(d.name);
|
|
517
|
-
this.decls.set(name, d);
|
|
518
|
-
this.values[name] = structuredClone(d.default ?? defaultFor(d));
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
get(name) {
|
|
522
|
-
return this.values[this.norm(name)];
|
|
523
|
-
}
|
|
524
|
-
/** A name as this bag keys it: its normalisation policy applied. The registry
|
|
525
|
-
* uses it to key quality ladders and the validation schema the bag's own way,
|
|
526
|
-
* so a case-significant (identity) bag is not quietly folded to lower case
|
|
527
|
-
* one layer up. */
|
|
528
|
-
normalise(name) {
|
|
529
|
-
return this.norm(name);
|
|
530
|
-
}
|
|
531
|
-
/** Write a property. Engine writes (the default) notify subscribers;
|
|
532
|
-
* pass `silent: true` for a host write, which reaches only the audit
|
|
533
|
-
* hook. Throws on a read-only property unless the caller says it is the
|
|
534
|
-
* HOST (`host: true`), for whom `writable: false` was never a rule - it is
|
|
535
|
-
* the story's promise, not the game's. `silent` and `host` are separate on
|
|
536
|
-
* purpose: one is about who hears the write, the other about who may make
|
|
537
|
-
* it. Returns the change. */
|
|
538
|
-
set(name, value, opts) {
|
|
539
|
-
const n = this.norm(name);
|
|
540
|
-
if (!opts?.host && this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);
|
|
541
|
-
const change = {
|
|
542
|
-
name: n,
|
|
543
|
-
prev: this.values[n],
|
|
544
|
-
next: value,
|
|
545
|
-
silent: opts?.silent ?? false,
|
|
546
|
-
reason: opts?.reason
|
|
547
|
-
};
|
|
548
|
-
this.values[n] = value;
|
|
549
|
-
for (const audit of this.auditors) audit(change);
|
|
550
|
-
if (!change.silent) for (const fn of this.subscribers) fn(change);
|
|
551
|
-
return change;
|
|
552
|
-
}
|
|
553
|
-
/** Notified of engine (non-silent) writes. Returns the unsubscribe. */
|
|
554
|
-
subscribe(fn) {
|
|
555
|
-
this.subscribers.add(fn);
|
|
556
|
-
return () => this.subscribers.delete(fn);
|
|
557
|
-
}
|
|
558
|
-
/** Notified of EVERY write, silent or not. Returns the unsubscribe. */
|
|
559
|
-
onAudit(fn) {
|
|
560
|
-
this.auditors.add(fn);
|
|
561
|
-
return () => this.auditors.delete(fn);
|
|
562
|
-
}
|
|
563
|
-
/** Examiner rows: the declared surface only (stray values are storage,
|
|
564
|
-
* not surface). */
|
|
565
|
-
rows() {
|
|
566
|
-
return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), void 0, name, this.pathPrefix));
|
|
567
|
-
}
|
|
568
|
-
declarations() {
|
|
569
|
-
return [...this.decls.values()];
|
|
570
|
-
}
|
|
571
|
-
/** The one sanctioned copy door: values deep-copied, declarations
|
|
572
|
-
* duplicated, the normalisation policy carried, subscriptions NOT
|
|
573
|
-
* carried. */
|
|
574
|
-
clone() {
|
|
575
|
-
const c = new _PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });
|
|
576
|
-
c.decls = new Map(this.decls);
|
|
577
|
-
Object.assign(c.values, structuredClone(this.values));
|
|
578
|
-
return c;
|
|
579
|
-
}
|
|
580
|
-
/** Clear and re-seed from new declarations, in place (the values record
|
|
581
|
-
* keeps its identity, so contexts built over it stay valid). */
|
|
582
|
-
reseed(declarations) {
|
|
583
|
-
for (const k of Object.keys(this.values)) delete this.values[k];
|
|
584
|
-
this.decls.clear();
|
|
585
|
-
this.seed(declarations);
|
|
586
|
-
}
|
|
587
|
-
/** Bare values, ready to embed in a product's save. */
|
|
588
|
-
save() {
|
|
589
|
-
return structuredClone(this.values);
|
|
590
|
-
}
|
|
591
|
-
/** Lay saved values over the current ones (call after a fresh seed:
|
|
592
|
-
* orphans land as strays, new declarations keep their defaults; the
|
|
593
|
-
* product decides whether to prune). Does not fire events. */
|
|
594
|
-
load(values) {
|
|
595
|
-
for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
function rowFor(d, value, writable, name, pathPrefix = "") {
|
|
599
|
-
const rowName = name ?? d.name.toLowerCase();
|
|
600
|
-
return {
|
|
601
|
-
name: rowName,
|
|
602
|
-
path: pathPrefix + rowName,
|
|
603
|
-
type: d.type,
|
|
604
|
-
value,
|
|
605
|
-
default: d.default ?? defaultFor(d),
|
|
606
|
-
...d.values !== void 0 ? { values: d.values } : {},
|
|
607
|
-
// `stages` was added to the row so an examiner could offer a quality's ladder
|
|
608
|
-
// instead of a free-text box, and then never populated here: every quality row
|
|
609
|
-
// this function built came out without one. Fixed 2026-09-02.
|
|
610
|
-
...d.stages !== void 0 ? { stages: d.stages } : {},
|
|
611
|
-
writable: writable ?? d.writable ?? true
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
var lowerCase = (name) => name.toLowerCase();
|
|
615
|
-
var SAVE_FRAGMENT_VERSION = 1;
|
|
616
|
-
var ScopeRegistry = class {
|
|
617
|
-
scopes = /* @__PURE__ */ new Map();
|
|
618
|
-
/** Values loaded for keys nobody has registered yet, waiting to be claimed. */
|
|
619
|
-
parked = /* @__PURE__ */ new Map();
|
|
620
|
-
rev = 0;
|
|
621
|
-
/**
|
|
622
|
-
* A counter that moves whenever a scope is registered or removed, and at no
|
|
623
|
-
* other time: it starts at 0 and each registration or removal adds 1. Values
|
|
624
|
-
* changing does not move it. A caller that caches a context built by
|
|
625
|
-
* `toEvalContext()` rebuilds it when this moves, because the context's set of
|
|
626
|
-
* scopes is fixed when it is built while the values it reads stay live.
|
|
627
|
-
*/
|
|
628
|
-
get revision() {
|
|
629
|
-
return this.rev;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Register a scope this registry **owns and stores**. Its bag is seeded from
|
|
633
|
-
* each declaration's `default` (or a type default). Owned scopes are
|
|
634
|
-
* type-checked (declarations) and serialized by `save`/`load`.
|
|
635
|
-
*
|
|
636
|
-
* The third argument may be the path prefix alone (the pre-0.7 form) or an
|
|
637
|
-
* options object.
|
|
638
|
-
*/
|
|
639
|
-
defineOwned(token, declarations, opts) {
|
|
640
|
-
const o = typeof opts === "string" ? { pathPrefix: opts } : opts ?? {};
|
|
641
|
-
const bag = new PropertyBag(declarations, {
|
|
642
|
-
pathPrefix: o.pathPrefix ?? `${token}.`,
|
|
643
|
-
...o.normalise ? { normalise: o.normalise } : {}
|
|
644
|
-
});
|
|
645
|
-
return this.mountOwned(token, bag, o.owner !== void 0 ? { owner: o.owner } : void 0);
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Attach an EXISTING bag as an owned scope: an engine (or a host) holds the
|
|
649
|
-
* bag and this registry reads, writes, lists and saves it like its own.
|
|
650
|
-
*
|
|
651
|
-
* If values were loaded for this key before anyone registered it, the bag
|
|
652
|
-
* claims them now: laid over its seeded defaults by the bag's own `load` rule.
|
|
653
|
-
*/
|
|
654
|
-
mountOwned(token, bag, opts) {
|
|
655
|
-
this.assertFree(token, opts?.owner);
|
|
656
|
-
this.scopes.set(token, { kind: "owned", bag, ...opts?.owner !== void 0 ? { owner: opts.owner } : {} });
|
|
657
|
-
this.rev++;
|
|
658
|
-
const waiting = this.parked.get(token);
|
|
659
|
-
if (waiting) {
|
|
660
|
-
bag.load(waiting);
|
|
661
|
-
this.parked.delete(token);
|
|
662
|
-
}
|
|
663
|
-
return this;
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Unregister a scope. With `{ keep: true }` an owned scope's values are parked
|
|
667
|
-
* and handed back when the same key is next registered, which is how a live
|
|
668
|
-
* reload hands an engine's state to its replacement. Throws on an unknown key.
|
|
669
|
-
*/
|
|
670
|
-
remove(token, opts) {
|
|
671
|
-
const e = this.scopes.get(token);
|
|
672
|
-
if (!e) throw new Error(`unknown scope '@${token}'`);
|
|
673
|
-
if (opts?.keep && e.kind === "owned") this.parked.set(token, e.bag.save());
|
|
674
|
-
this.scopes.delete(token);
|
|
675
|
-
this.rev++;
|
|
676
|
-
return this;
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Drop parked values nobody claimed. Parked values are kept in the next save by
|
|
680
|
-
* default, so nothing loaded is lost to a flow or deck that simply has not
|
|
681
|
-
* reopened yet; a game that knows they are dead drops them here.
|
|
682
|
-
*
|
|
683
|
-
* With a `prefix`, only keys starting with it are dropped: an engine resetting
|
|
684
|
-
* itself drops its own instance keys (`my-engine/`) and leaves every other
|
|
685
|
-
* engine's alone.
|
|
686
|
-
*/
|
|
687
|
-
discardParked(prefix) {
|
|
688
|
-
if (prefix === void 0) this.parked.clear();
|
|
689
|
-
else for (const key of [...this.parked.keys()]) if (key.startsWith(prefix)) this.parked.delete(key);
|
|
690
|
-
return this;
|
|
691
|
-
}
|
|
692
|
-
/** An owned scope's bag (subscribe, audit, rows live there). */
|
|
693
|
-
ownedBag(token) {
|
|
694
|
-
const e = this.scopes.get(token);
|
|
695
|
-
if (!e || e.kind !== "owned") throw new Error(`'@${token}' is not an owned scope`);
|
|
696
|
-
return e.bag;
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Re-initialise an existing **owned** scope's bag from new declarations,
|
|
700
|
-
* clearing its current values. For scope-local state that resets on a context
|
|
701
|
-
* change (e.g. entering a new scene / site / deck) without disturbing other
|
|
702
|
-
* scopes. Mutates the bag in place, so an `EvalContext` already built from this
|
|
703
|
-
* registry stays valid.
|
|
704
|
-
*/
|
|
705
|
-
reseedOwned(token, declarations) {
|
|
706
|
-
this.ownedBag(token).reseed(declarations);
|
|
707
|
-
return this;
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Register a **foreign** scope backed by a host `{ get, set? }` resolver. The
|
|
711
|
-
* values live in the host/other engine and are never stored or saved here.
|
|
712
|
-
* `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used
|
|
713
|
-
* only for validation; omit them for an opaque scope.
|
|
714
|
-
*/
|
|
715
|
-
defineForeign(token, resolver, declarations = [], opts = true) {
|
|
716
|
-
const o = typeof opts === "boolean" ? { writable: opts } : opts;
|
|
717
|
-
this.assertFree(token, o.owner);
|
|
718
|
-
const norm = o.normalise ?? lowerCase;
|
|
719
|
-
const decls = /* @__PURE__ */ new Map();
|
|
720
|
-
for (const d of declarations) decls.set(norm(d.name), d);
|
|
721
|
-
this.scopes.set(token, {
|
|
722
|
-
kind: "foreign",
|
|
723
|
-
resolver,
|
|
724
|
-
decls,
|
|
725
|
-
scopeWritable: o.writable ?? true,
|
|
726
|
-
norm,
|
|
727
|
-
...o.owner !== void 0 ? { owner: o.owner } : {}
|
|
728
|
-
});
|
|
729
|
-
this.rev++;
|
|
730
|
-
return this;
|
|
731
|
-
}
|
|
732
|
-
has(token) {
|
|
733
|
-
return this.scopes.has(token);
|
|
734
|
-
}
|
|
735
|
-
/** Read a property; undefined if the scope or property is not present. */
|
|
736
|
-
get(scope, name) {
|
|
737
|
-
const e = this.scopes.get(scope);
|
|
738
|
-
if (!e) return void 0;
|
|
739
|
-
return e.kind === "owned" ? e.bag.get(name) : e.resolver.get(e.norm(name));
|
|
740
|
-
}
|
|
741
|
-
/** Write a property (an ENGINE write: the bag's subscribers fire; use
|
|
742
|
-
* the bag directly for silent host writes). Throws on an unknown scope.
|
|
743
|
-
*
|
|
744
|
-
* `writable: false` is the STORY's promise, so a story write is refused and
|
|
745
|
-
* a HOST write is not: pass `{ host: true }` from a host's own surface (its
|
|
746
|
-
* `setProperty`, its tooling, a coverage driver) and never from the path an
|
|
747
|
-
* outcome or effect takes. A foreign scope whose resolver has no `set` is
|
|
748
|
-
* refused for everyone, host included - that is not a rule to bypass, it is
|
|
749
|
-
* a game that gave no way to write. */
|
|
750
|
-
set(scope, name, value, opts) {
|
|
751
|
-
const e = this.scopes.get(scope);
|
|
752
|
-
if (!e) throw new Error(`unknown scope '@${scope}'`);
|
|
753
|
-
if (e.kind === "owned") {
|
|
754
|
-
try {
|
|
755
|
-
e.bag.set(name, value, opts?.host ? { host: true } : void 0);
|
|
756
|
-
} catch {
|
|
757
|
-
throw new Error(`'@${scope}.${name}' is read-only`);
|
|
758
|
-
}
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
const n = e.norm(name);
|
|
762
|
-
if (!e.resolver.set) throw new Error(`'@${scope}.${name}' is read-only`);
|
|
763
|
-
if (!opts?.host && !this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);
|
|
764
|
-
e.resolver.set(n, value);
|
|
765
|
-
}
|
|
766
|
-
foreignWritable(e, name) {
|
|
767
|
-
if (!e.resolver.set) return false;
|
|
768
|
-
return e.decls.get(name)?.writable ?? e.scopeWritable;
|
|
769
|
-
}
|
|
770
|
-
/** Examiner rows across every scope with a declared surface: owned bags
|
|
771
|
-
* first, then declared foreign scopes (values read through, writability
|
|
772
|
-
* reflecting the resolver). Opaque foreign scopes are not listed. */
|
|
773
|
-
listProperties() {
|
|
774
|
-
const out = [];
|
|
775
|
-
for (const [token, e] of this.scopes) {
|
|
776
|
-
const owner = e.owner !== void 0 ? { owner: e.owner } : {};
|
|
777
|
-
if (e.kind === "owned") {
|
|
778
|
-
for (const row of e.bag.rows()) out.push({ scope: token, ...owner, ...row });
|
|
779
|
-
} else {
|
|
780
|
-
for (const [n, d] of e.decls) {
|
|
781
|
-
out.push({
|
|
782
|
-
scope: token,
|
|
783
|
-
...owner,
|
|
784
|
-
...rowFor(d, e.resolver.get(n), this.foreignWritable(e, n), n, `${token}.`)
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return out;
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static
|
|
793
|
-
* bags, foreign scopes as their resolvers. `host` carries dialect-function
|
|
794
|
-
* callbacks (PRNG, tag lookups) and is passed through untouched.
|
|
795
|
-
*/
|
|
796
|
-
toEvalContext(host2, opts) {
|
|
797
|
-
const view = this.view(opts?.aliases);
|
|
798
|
-
const scopes = {};
|
|
799
|
-
for (const [token, e] of view) scopes[token] = e.kind === "owned" ? e.bag.values : e.resolver;
|
|
800
|
-
const qualities = this.qualityLadders(view);
|
|
801
|
-
return qualities.size === 0 ? { scopes, host: host2 } : {
|
|
802
|
-
scopes,
|
|
803
|
-
host: host2,
|
|
804
|
-
qualities: (scope, name) => {
|
|
805
|
-
const e = view.get(scope);
|
|
806
|
-
return e ? qualities.get(scope)?.get(normOf(e)(name)) : void 0;
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* The scopes an expression sees: every registered key under its own token,
|
|
812
|
-
* then each alias token pointing at its key's entry (an alias shadows a key of
|
|
813
|
-
* the same name). Keys an engine uses for instance bags (`engine/flow-2/...`)
|
|
814
|
-
* are not valid expression tokens, so they are present but unreachable.
|
|
815
|
-
*/
|
|
816
|
-
view(aliases) {
|
|
817
|
-
const out = new Map(this.scopes);
|
|
818
|
-
for (const [token, key] of Object.entries(aliases ?? {})) {
|
|
819
|
-
const e = this.scopes.get(key);
|
|
820
|
-
if (!e) throw new Error(`alias '@${token}' names '${key}', which is not registered`);
|
|
821
|
-
out.set(token, e);
|
|
822
|
-
}
|
|
823
|
-
return out;
|
|
824
|
-
}
|
|
825
|
-
/** Every quality declaration's ladder, keyed scope token then name (the
|
|
826
|
-
* scope's own normalisation). */
|
|
827
|
-
qualityLadders(view) {
|
|
828
|
-
const out = /* @__PURE__ */ new Map();
|
|
829
|
-
for (const [token, e] of view) {
|
|
830
|
-
for (const [n, d] of declsOf(e)) {
|
|
831
|
-
if (d.type !== "quality" || d.stages === void 0) continue;
|
|
832
|
-
let m = out.get(token);
|
|
833
|
-
if (!m) {
|
|
834
|
-
m = /* @__PURE__ */ new Map();
|
|
835
|
-
out.set(token, m);
|
|
836
|
-
}
|
|
837
|
-
m.set(n, d.stages);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return out;
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Build the `ExpressionSchema` expr's validator consumes. Scopes with no
|
|
844
|
-
* declarations are **omitted** (opaque - references into them are not flagged);
|
|
845
|
-
* declared scopes contribute their property types for validation. Aliases
|
|
846
|
-
* apply as they do to `toEvalContext`, so a condition written against `@scene`
|
|
847
|
-
* validates against the instance bag the engine names.
|
|
848
|
-
*/
|
|
849
|
-
toSchema(opts) {
|
|
850
|
-
const properties = /* @__PURE__ */ new Map();
|
|
851
|
-
for (const [token, e] of this.view(opts?.aliases)) {
|
|
852
|
-
const decls = declsOf(e);
|
|
853
|
-
if (decls.length === 0) continue;
|
|
854
|
-
const m = /* @__PURE__ */ new Map();
|
|
855
|
-
for (const [n, d] of decls) m.set(n, {
|
|
856
|
-
type: d.type,
|
|
857
|
-
enumValues: d.values,
|
|
858
|
-
...d.stages !== void 0 ? { stages: d.stages } : {}
|
|
859
|
-
});
|
|
860
|
-
properties.set(token, m);
|
|
861
|
-
}
|
|
862
|
-
return { properties };
|
|
863
|
-
}
|
|
864
|
-
/** Serialize **owned** scopes (foreign scopes are the game's, and the game
|
|
865
|
-
* saves them), as bare bags keyed by token, plus any values still parked, so
|
|
866
|
-
* a save taken before every engine has re-registered loses nothing. The
|
|
867
|
-
* registry knows nothing about game saves: a game embeds this in its own. */
|
|
868
|
-
save() {
|
|
869
|
-
const out = {};
|
|
870
|
-
for (const [token, e] of this.scopes) if (e.kind === "owned") out[token] = e.bag.save();
|
|
871
|
-
for (const [token, vals] of this.parked) out[token] = structuredClone(vals);
|
|
872
|
-
return out;
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Restore from a `save` blob. An owned scope lays its section over its current
|
|
876
|
-
* values (the bag's `load` rule). A section for a key nobody has registered
|
|
877
|
-
* yet is PARKED and handed over when that key registers, so a game can load
|
|
878
|
-
* its registry before its engines have reopened their flows or decks. A
|
|
879
|
-
* section for a foreign scope is ignored: those values are the game's.
|
|
880
|
-
*
|
|
881
|
-
* A load replaces whatever was parked before it: it is a whole restore, and
|
|
882
|
-
* residue from an earlier load must not leak into this one.
|
|
883
|
-
*
|
|
884
|
-
* Changed in 0.7.0: sections for unregistered keys used to be dropped.
|
|
885
|
-
*/
|
|
886
|
-
load(blob, opts) {
|
|
887
|
-
if (!opts?.keepParked) this.parked.clear();
|
|
888
|
-
for (const [token, vals] of Object.entries(blob)) {
|
|
889
|
-
const e = this.scopes.get(token);
|
|
890
|
-
if (e?.kind === "owned") e.bag.load(vals);
|
|
891
|
-
else if (!e) this.parked.set(token, structuredClone(vals));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* `save()` wrapped with a version stamp.
|
|
896
|
-
*
|
|
897
|
-
* @deprecated Versioning belongs to the save that embeds the values; no
|
|
898
|
-
* engine ever called this. Embed `save()` in your own versioned save.
|
|
899
|
-
* Removed at the next breaking release.
|
|
900
|
-
*/
|
|
901
|
-
saveFragment() {
|
|
902
|
-
return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Restore from a versioned fragment; an unsupported version throws.
|
|
906
|
-
*
|
|
907
|
-
* @deprecated See `saveFragment`. Removed at the next breaking release.
|
|
908
|
-
*/
|
|
909
|
-
loadFragment(fragment) {
|
|
910
|
-
if (fragment.version !== SAVE_FRAGMENT_VERSION) {
|
|
911
|
-
throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);
|
|
912
|
-
}
|
|
913
|
-
this.load(fragment.scopes);
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* A token is taken once. There is no reserved-token list: a clash surfaces
|
|
917
|
-
* here, the moment a game combines its engines, which is the only moment
|
|
918
|
-
* anyone knows which engines are present. With owners recorded the error says
|
|
919
|
-
* whose token it already is.
|
|
920
|
-
*/
|
|
921
|
-
assertFree(token, owner) {
|
|
922
|
-
const e = this.scopes.get(token);
|
|
923
|
-
if (!e) return;
|
|
924
|
-
const by = e.owner !== void 0 ? ` by ${e.owner}` : "";
|
|
925
|
-
const wants = owner !== void 0 ? ` (wanted by ${owner})` : "";
|
|
926
|
-
throw new Error(`scope '@${token}' is already registered${by}${wants}`);
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
function declsOf(e) {
|
|
930
|
-
if (e.kind === "foreign") return [...e.decls.entries()];
|
|
931
|
-
return e.bag.declarations().map((d) => [e.bag.normalise(d.name), d]);
|
|
932
|
-
}
|
|
933
|
-
function normOf(e) {
|
|
934
|
-
return e.kind === "foreign" ? e.norm : (n) => e.bag.normalise(n);
|
|
935
|
-
}
|
|
936
|
-
function defaultFor(d) {
|
|
937
|
-
if (d.default !== void 0) return d.default;
|
|
938
|
-
switch (d.type) {
|
|
939
|
-
case "boolean":
|
|
940
|
-
return false;
|
|
941
|
-
case "number":
|
|
942
|
-
return 0;
|
|
943
|
-
case "string":
|
|
944
|
-
return "";
|
|
945
|
-
case "enum":
|
|
946
|
-
return d.values?.[0] ?? "";
|
|
947
|
-
case "flags":
|
|
948
|
-
return [];
|
|
949
|
-
// A quality starts at the first rung of its ladder.
|
|
950
|
-
case "quality":
|
|
951
|
-
return d.stages?.[0] ?? "";
|
|
952
|
-
// Unreachable for a well-typed declaration, and deliberately present anyway: a bundle
|
|
953
|
-
// is DATA, and a hand-edited or newer-than-this-build one can carry a type string the
|
|
954
|
-
// union does not have. Falling off the switch would seed `undefined`, which is not a
|
|
955
|
-
// ScalarValue and travels a long way before it fails. Patterplay's copy of this had the
|
|
956
|
-
// guard and this one did not, which is the drift you only find by removing a duplicate.
|
|
957
|
-
default:
|
|
958
|
-
return false;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
39
|
+
// src/prng.ts
|
|
40
|
+
var import_expr = require("@wildwinter/expr");
|
|
961
41
|
|
|
962
42
|
// src/engine.ts
|
|
963
43
|
var tagKey = (groupId, tagId) => `${groupId}${tagId}`;
|
|
@@ -1005,7 +85,7 @@ function sectionsOf(p, keyOf, out) {
|
|
|
1005
85
|
for (const [id, values] of Object.entries(p[kind])) if (Object.keys(values).length > 0) out[keyOf(kind, id)] = values;
|
|
1006
86
|
}
|
|
1007
87
|
}
|
|
1008
|
-
var bagFromDecls = (decls, pathPrefix) => new PropertyBag(decls, { normalise: (n) => n, pathPrefix });
|
|
88
|
+
var bagFromDecls = (decls, pathPrefix) => new import_scoperegistry.PropertyBag(decls, { normalise: (n) => n, pathPrefix });
|
|
1009
89
|
function conditionPasses(v) {
|
|
1010
90
|
if (typeof v === "boolean") return v;
|
|
1011
91
|
if (typeof v === "number") return v !== 0;
|
|
@@ -1024,12 +104,12 @@ var emptyOwnerIndexes = () => ({
|
|
|
1024
104
|
value: { gameId: /* @__PURE__ */ new Map(), id: /* @__PURE__ */ new Map(), repeated: /* @__PURE__ */ new Map() }
|
|
1025
105
|
});
|
|
1026
106
|
var indexOwner = (index, entity) => {
|
|
1027
|
-
const gameId = effectiveGameId(entity);
|
|
107
|
+
const gameId = (0, import_model.effectiveGameId)(entity);
|
|
1028
108
|
index.gameId.set(entity.id, gameId);
|
|
1029
109
|
if (!index.id.has(gameId)) index.id.set(gameId, entity.id);
|
|
1030
110
|
};
|
|
1031
111
|
var indexValueOwners = (index, bundle) => {
|
|
1032
|
-
const addresses = valueAddresses(bundle);
|
|
112
|
+
const addresses = (0, import_model.valueAddresses)(bundle);
|
|
1033
113
|
for (const [id, segment] of addresses.print) index.gameId.set(id, segment);
|
|
1034
114
|
for (const [segment, id] of addresses.accept) index.id.set(segment, id);
|
|
1035
115
|
for (const [gameId, candidates] of addresses.repeated) index.repeated.set(gameId, candidates);
|
|
@@ -1052,7 +132,7 @@ var resolveOwner = (internals, kind, segment) => {
|
|
|
1052
132
|
var ownerOrThrow = (internals, kind, segment, name) => {
|
|
1053
133
|
const owner = resolveOwner(internals, kind, segment);
|
|
1054
134
|
if (owner === void 0) throw new Error(`no ${kind} store "${segment}"`);
|
|
1055
|
-
if ("ambiguous" in owner) throw new Error(ambiguousValueAddressMessage(segment, name, owner.ambiguous));
|
|
135
|
+
if ("ambiguous" in owner) throw new Error((0, import_model.ambiguousValueAddressMessage)(segment, name, owner.ambiguous));
|
|
1056
136
|
return owner;
|
|
1057
137
|
};
|
|
1058
138
|
var legacyAddressMessage = (internals, kind, segment, name) => `"${kind}.${segment}.${name}" names the ${kind} by its internal id; write "${addressOf(internals, kind, segment)}.${name}". The internal-id form is refused after the next release.`;
|
|
@@ -1229,7 +309,7 @@ var Engine = class _Engine {
|
|
|
1229
309
|
flowDecls: { story: [], box: /* @__PURE__ */ new Map(), deck: /* @__PURE__ */ new Map(), hand: /* @__PURE__ */ new Map(), value: /* @__PURE__ */ new Map() },
|
|
1230
310
|
sharedDecls: { story: [], box: /* @__PURE__ */ new Map(), deck: /* @__PURE__ */ new Map(), hand: /* @__PURE__ */ new Map(), value: /* @__PURE__ */ new Map() },
|
|
1231
311
|
shared: void 0,
|
|
1232
|
-
registry: opts.registry ?? new ScopeRegistry(),
|
|
312
|
+
registry: opts.registry ?? new import_scoperegistry.ScopeRegistry(),
|
|
1233
313
|
ownsRegistry: opts.registry === void 0,
|
|
1234
314
|
selfWorld: false,
|
|
1235
315
|
registryView: () => view(),
|
|
@@ -1263,7 +343,7 @@ var Engine = class _Engine {
|
|
|
1263
343
|
indexValueOwners(internals.owners.value, bundle);
|
|
1264
344
|
for (const box of bundle.boxes) {
|
|
1265
345
|
internals.boxesById.set(box.id, box);
|
|
1266
|
-
internals.boxesByGameId.set(effectiveGameId(box), box);
|
|
346
|
+
internals.boxesByGameId.set((0, import_model.effectiveGameId)(box), box);
|
|
1267
347
|
indexOwner(internals.owners.box, box);
|
|
1268
348
|
for (const group of box.tagGroups) {
|
|
1269
349
|
internals.groupsById.set(group.id, { group, box });
|
|
@@ -1275,7 +355,7 @@ var Engine = class _Engine {
|
|
|
1275
355
|
for (const card of deck.cards) {
|
|
1276
356
|
const entry = { card, deck, box };
|
|
1277
357
|
internals.cardsById.set(card.id, entry);
|
|
1278
|
-
internals.cardsByGameId.set(effectiveGameId(card), entry);
|
|
358
|
+
internals.cardsByGameId.set((0, import_model.effectiveGameId)(card), entry);
|
|
1279
359
|
if (card.shared === true) internals.hasShared = true;
|
|
1280
360
|
}
|
|
1281
361
|
}
|
|
@@ -1284,7 +364,7 @@ var Engine = class _Engine {
|
|
|
1284
364
|
}
|
|
1285
365
|
for (const hand of box.hands) {
|
|
1286
366
|
internals.handsById.set(hand.id, { hand, box });
|
|
1287
|
-
internals.handsByGameId.set(effectiveGameId(hand), { hand, box });
|
|
367
|
+
internals.handsByGameId.set((0, import_model.effectiveGameId)(hand), { hand, box });
|
|
1288
368
|
indexOwner(internals.owners.hand, hand);
|
|
1289
369
|
}
|
|
1290
370
|
}
|
|
@@ -1362,8 +442,8 @@ var Engine = class _Engine {
|
|
|
1362
442
|
reg.set("world", n, v, { host: true });
|
|
1363
443
|
} : void 0;
|
|
1364
444
|
} else {
|
|
1365
|
-
internals.worldSet = (n, v,
|
|
1366
|
-
reg.set("world", n, v,
|
|
445
|
+
internals.worldSet = (n, v, host) => {
|
|
446
|
+
reg.set("world", n, v, host === true ? { host: true } : void 0);
|
|
1367
447
|
};
|
|
1368
448
|
}
|
|
1369
449
|
}
|
|
@@ -1722,7 +802,7 @@ var Engine = class _Engine {
|
|
|
1722
802
|
* saves it once itself, beside each engine's envelope. */
|
|
1723
803
|
saveGame() {
|
|
1724
804
|
return structuredClone({
|
|
1725
|
-
schema: SAVE_SCHEMA,
|
|
805
|
+
schema: import_model2.SAVE_SCHEMA,
|
|
1726
806
|
content: this.internals.bundle.content,
|
|
1727
807
|
...this.internals.ownsRegistry ? { registry: this.internals.registry.save() } : {},
|
|
1728
808
|
shared: { spent: [...this.spent].sort() },
|
|
@@ -1798,11 +878,11 @@ var Engine = class _Engine {
|
|
|
1798
878
|
* previewLoad and loadGame share it. */
|
|
1799
879
|
planLoad(envelope) {
|
|
1800
880
|
const schema = envelope.schema;
|
|
1801
|
-
if (schema !== SAVE_SCHEMA && schema !== SAVE_SCHEMA_V1) throw new Error(`unsupported save schema: ${String(schema)}`);
|
|
881
|
+
if (schema !== import_model2.SAVE_SCHEMA && schema !== import_model2.SAVE_SCHEMA_V1) throw new Error(`unsupported save schema: ${String(schema)}`);
|
|
1802
882
|
const draft = emptyDraft();
|
|
1803
883
|
const flowIds = new Set(Object.keys(envelope.flows ?? {}));
|
|
1804
884
|
let moved;
|
|
1805
|
-
if (envelope.schema === SAVE_SCHEMA_V1) {
|
|
885
|
+
if (envelope.schema === import_model2.SAVE_SCHEMA_V1) {
|
|
1806
886
|
moved = {
|
|
1807
887
|
shared: envelope.shared?.props ?? emptyPartitionValues(),
|
|
1808
888
|
flows: new Map(Object.entries(envelope.flows ?? {}).map(([id, f]) => [id, f.props ?? emptyPartitionValues()])),
|
|
@@ -1852,7 +932,7 @@ var Engine = class _Engine {
|
|
|
1852
932
|
}
|
|
1853
933
|
const cardName = (cardId) => {
|
|
1854
934
|
const entry = internals.cardsById.get(cardId);
|
|
1855
|
-
return entry ? effectiveGameId(entry.card) : cardId;
|
|
935
|
+
return entry ? (0, import_model.effectiveGameId)(entry.card) : cardId;
|
|
1856
936
|
};
|
|
1857
937
|
const board = {};
|
|
1858
938
|
const restored = /* @__PURE__ */ new Map();
|
|
@@ -1864,7 +944,7 @@ var Engine = class _Engine {
|
|
|
1864
944
|
}
|
|
1865
945
|
continue;
|
|
1866
946
|
}
|
|
1867
|
-
const hand = effectiveGameId(known.hand);
|
|
947
|
+
const hand = (0, import_model.effectiveGameId)(known.hand);
|
|
1868
948
|
const kept = [];
|
|
1869
949
|
for (const cardId of ids) {
|
|
1870
950
|
const entry = internals.cardsById.get(cardId);
|
|
@@ -1875,7 +955,7 @@ var Engine = class _Engine {
|
|
|
1875
955
|
if (otherClaims !== void 0 && cardIsShared(entry.card, entry.deck.shared ?? false)) {
|
|
1876
956
|
const held = (otherClaims.get(cardId) ?? 0) + (restored.get(cardId) ?? 0);
|
|
1877
957
|
if (held >= sharedCap(entry.card)) {
|
|
1878
|
-
draft.evicted.push({ flow: id, hand, card: effectiveGameId(entry.card), reason: "claimed-elsewhere" });
|
|
958
|
+
draft.evicted.push({ flow: id, hand, card: (0, import_model.effectiveGameId)(entry.card), reason: "claimed-elsewhere" });
|
|
1879
959
|
continue;
|
|
1880
960
|
}
|
|
1881
961
|
restored.set(cardId, (restored.get(cardId) ?? 0) + 1);
|
|
@@ -1949,7 +1029,7 @@ var Flow = class {
|
|
|
1949
1029
|
this.engine = engine;
|
|
1950
1030
|
this.internals = internals;
|
|
1951
1031
|
this.id = id;
|
|
1952
|
-
this.prng = makePrng(seed);
|
|
1032
|
+
this.prng = (0, import_expr.makePrng)(seed);
|
|
1953
1033
|
this.stores = buildPartition(internals, flowHalf);
|
|
1954
1034
|
const put = (key, bag) => {
|
|
1955
1035
|
if (bag.declarations().length === 0) return;
|
|
@@ -2050,7 +1130,7 @@ var Flow = class {
|
|
|
2050
1130
|
node(expr) {
|
|
2051
1131
|
let node = this.internals.nodeCache.get(expr);
|
|
2052
1132
|
if (!node) {
|
|
2053
|
-
node = deserialiseAst(expr.ast);
|
|
1133
|
+
node = (0, import_expr2.deserialiseAst)(expr.ast);
|
|
2054
1134
|
this.internals.nodeCache.set(expr, node);
|
|
2055
1135
|
}
|
|
2056
1136
|
return node;
|
|
@@ -2060,7 +1140,7 @@ var Flow = class {
|
|
|
2060
1140
|
* resolved inside the box being asked, never bundle-wide. Ids are
|
|
2061
1141
|
* project-unique and accepted here too, still confined to the box. */
|
|
2062
1142
|
groupInBox(box, ref) {
|
|
2063
|
-
return box.tagGroups.find((g) => effectiveGameId(g) === ref) ?? box.tagGroups.find((g) => g.id === ref);
|
|
1143
|
+
return box.tagGroups.find((g) => (0, import_model.effectiveGameId)(g) === ref) ?? box.tagGroups.find((g) => g.id === ref);
|
|
2064
1144
|
}
|
|
2065
1145
|
/** Fold one play into the indexes. O(the card's tags), not O(the log). */
|
|
2066
1146
|
indexPlay(record) {
|
|
@@ -2116,7 +1196,7 @@ var Flow = class {
|
|
|
2116
1196
|
};
|
|
2117
1197
|
const since = (record) => {
|
|
2118
1198
|
const entry = this.internals.cardsByGameId.get(record.card);
|
|
2119
|
-
if (!entry) return NEVER_PLAYED;
|
|
1199
|
+
if (!entry) return import_dialect.NEVER_PLAYED;
|
|
2120
1200
|
return (this.turnCounts.get(entry.box.id) ?? 0) - record.turn;
|
|
2121
1201
|
};
|
|
2122
1202
|
return {
|
|
@@ -2124,7 +1204,7 @@ var Flow = class {
|
|
|
2124
1204
|
countPlayed: (card) => this.playCount.get(card) ?? 0,
|
|
2125
1205
|
turnsSincePlayed: (card) => {
|
|
2126
1206
|
const last = this.lastPlayOf.get(card);
|
|
2127
|
-
return last ? since(last) : NEVER_PLAYED;
|
|
1207
|
+
return last ? since(last) : import_dialect.NEVER_PLAYED;
|
|
2128
1208
|
},
|
|
2129
1209
|
countPlayedIn: (group, tag) => {
|
|
2130
1210
|
const key = keyOf(group, tag);
|
|
@@ -2133,7 +1213,7 @@ var Flow = class {
|
|
|
2133
1213
|
turnsSincePlayedIn: (group, tag) => {
|
|
2134
1214
|
const key = keyOf(group, tag);
|
|
2135
1215
|
const last = key === void 0 ? void 0 : this.lastPlayInTag.get(key);
|
|
2136
|
-
return last ? since(last) : NEVER_PLAYED;
|
|
1216
|
+
return last ? since(last) : import_dialect.NEVER_PLAYED;
|
|
2137
1217
|
}
|
|
2138
1218
|
};
|
|
2139
1219
|
}
|
|
@@ -2172,7 +1252,7 @@ var Flow = class {
|
|
|
2172
1252
|
return source.kind === "value" ? this.internals.ladders.value.get(source.id)?.get(name) : source.kind === "hand" ? this.internals.ladders.hand.get(source.id)?.get(name) : void 0;
|
|
2173
1253
|
}
|
|
2174
1254
|
eval(expr, ctx) {
|
|
2175
|
-
return evaluate(this.node(expr), ctx, storyletsDialect);
|
|
1255
|
+
return (0, import_expr2.evaluate)(this.node(expr), ctx, import_dialect.storyletsDialect);
|
|
2176
1256
|
}
|
|
2177
1257
|
passes(expr, ctx, where) {
|
|
2178
1258
|
if (!expr) return true;
|
|
@@ -2197,35 +1277,35 @@ var Flow = class {
|
|
|
2197
1277
|
let condition;
|
|
2198
1278
|
if (hand.template !== void 0) {
|
|
2199
1279
|
const template = this.internals.templatesById.get(hand.template);
|
|
2200
|
-
if (!template) throw new Error(`hand "${effectiveGameId(hand)}": unknown template "${hand.template}"`);
|
|
1280
|
+
if (!template) throw new Error(`hand "${(0, import_model.effectiveGameId)(hand)}": unknown template "${hand.template}"`);
|
|
2201
1281
|
for (const [groupId, tagId] of Object.entries(template.bindings ?? {})) {
|
|
2202
1282
|
boundTags.set(groupId, tagId);
|
|
2203
1283
|
}
|
|
2204
1284
|
for (const [groupId, tagId] of Object.entries(hand.chosen ?? {})) {
|
|
2205
|
-
if (isHoleRef(tagId)) {
|
|
1285
|
+
if ((0, import_model.isHoleRef)(tagId)) {
|
|
2206
1286
|
this.fillHoleFromProperty(hand, groupId, tagId, boundTags, askNames);
|
|
2207
1287
|
continue;
|
|
2208
1288
|
}
|
|
2209
1289
|
boundTags.set(groupId, tagId);
|
|
2210
1290
|
const found = this.internals.groupsById.get(groupId);
|
|
2211
1291
|
const tag = found?.group.tags.find((t) => t.id === tagId);
|
|
2212
|
-
if (found && tag) askNames[effectiveGameId(found.group)] = effectiveGameId(tag);
|
|
1292
|
+
if (found && tag) askNames[(0, import_model.effectiveGameId)(found.group)] = (0, import_model.effectiveGameId)(tag);
|
|
2213
1293
|
}
|
|
2214
1294
|
condition = template.condition;
|
|
2215
1295
|
} else {
|
|
2216
1296
|
for (const [groupId, tagId] of Object.entries(hand.rule?.bindings ?? {})) {
|
|
2217
|
-
if (isHoleRef(tagId)) {
|
|
1297
|
+
if ((0, import_model.isHoleRef)(tagId)) {
|
|
2218
1298
|
this.fillHoleFromProperty(hand, groupId, tagId, boundTags, askNames);
|
|
2219
1299
|
continue;
|
|
2220
1300
|
}
|
|
2221
1301
|
boundTags.set(groupId, tagId);
|
|
2222
1302
|
const found = this.internals.groupsById.get(groupId);
|
|
2223
1303
|
const tag = found?.group.tags.find((t) => t.id === tagId);
|
|
2224
|
-
if (found && tag) askNames[effectiveGameId(found.group)] = effectiveGameId(tag);
|
|
1304
|
+
if (found && tag) askNames[(0, import_model.effectiveGameId)(found.group)] = (0, import_model.effectiveGameId)(tag);
|
|
2225
1305
|
}
|
|
2226
1306
|
condition = hand.rule?.condition;
|
|
2227
1307
|
}
|
|
2228
|
-
boundTags.set(PLACE_GROUP, hand.id);
|
|
1308
|
+
boundTags.set(import_model.PLACE_GROUP, hand.id);
|
|
2229
1309
|
this.bindStateGroups(box, boundTags, askNames);
|
|
2230
1310
|
return { box, hand, ...condition !== void 0 ? { condition } : {}, boundTags, askNames };
|
|
2231
1311
|
}
|
|
@@ -2235,20 +1315,20 @@ var Flow = class {
|
|
|
2235
1315
|
const boundTags = /* @__PURE__ */ new Map();
|
|
2236
1316
|
const askNames = {};
|
|
2237
1317
|
for (const [groupRef, tagRef] of Object.entries(criteria)) {
|
|
2238
|
-
if (groupRef === PLACE_GROUP) {
|
|
1318
|
+
if (groupRef === import_model.PLACE_GROUP) {
|
|
2239
1319
|
const hand = this.internals.handsByGameId.get(tagRef) ?? this.internals.handsById.get(tagRef);
|
|
2240
1320
|
if (!hand) throw new Error(`peek: unknown hand "${tagRef}" in home criteria`);
|
|
2241
|
-
boundTags.set(PLACE_GROUP, hand.hand.id);
|
|
1321
|
+
boundTags.set(import_model.PLACE_GROUP, hand.hand.id);
|
|
2242
1322
|
continue;
|
|
2243
1323
|
}
|
|
2244
1324
|
const found = this.groupInBox(box, groupRef);
|
|
2245
1325
|
if (!found) {
|
|
2246
|
-
throw new Error(`peek: unknown tag group "${groupRef}" in box "${effectiveGameId(box)}"`);
|
|
1326
|
+
throw new Error(`peek: unknown tag group "${groupRef}" in box "${(0, import_model.effectiveGameId)(box)}"`);
|
|
2247
1327
|
}
|
|
2248
1328
|
const tag = this.tagByGameId(found, tagRef) ?? found.tags.find((t) => t.id === tagRef);
|
|
2249
|
-
if (!tag) throw new Error(`peek: unknown tag "${tagRef}" in group "${effectiveGameId(found)}"`);
|
|
1329
|
+
if (!tag) throw new Error(`peek: unknown tag "${tagRef}" in group "${(0, import_model.effectiveGameId)(found)}"`);
|
|
2250
1330
|
boundTags.set(found.id, tag.id);
|
|
2251
|
-
askNames[effectiveGameId(found)] = effectiveGameId(tag);
|
|
1331
|
+
askNames[(0, import_model.effectiveGameId)(found)] = (0, import_model.effectiveGameId)(tag);
|
|
2252
1332
|
}
|
|
2253
1333
|
this.bindStateGroups(box, boundTags, askNames);
|
|
2254
1334
|
return { box, boundTags, askNames };
|
|
@@ -2271,9 +1351,9 @@ var Flow = class {
|
|
|
2271
1351
|
*/
|
|
2272
1352
|
fillHoleFromProperty(hand, groupId, ref, boundTags, askNames) {
|
|
2273
1353
|
const found = this.internals.groupsById.get(groupId);
|
|
2274
|
-
const groupName = found ? effectiveGameId(found.group) : groupId;
|
|
2275
|
-
const where = `hand ${effectiveGameId(hand)}, tag group ${groupName}`;
|
|
2276
|
-
const parsed = parseHoleRef(ref);
|
|
1354
|
+
const groupName = found ? (0, import_model.effectiveGameId)(found.group) : groupId;
|
|
1355
|
+
const where = `hand ${(0, import_model.effectiveGameId)(hand)}, tag group ${groupName}`;
|
|
1356
|
+
const parsed = (0, import_model.parseHoleRef)(ref);
|
|
2277
1357
|
if (!parsed) {
|
|
2278
1358
|
this.emit({ type: "diagnostic", where, message: `"${ref}" is not a @hand, @world or @story property reference` });
|
|
2279
1359
|
return;
|
|
@@ -2297,13 +1377,13 @@ var Flow = class {
|
|
|
2297
1377
|
return;
|
|
2298
1378
|
}
|
|
2299
1379
|
const wanted = typeof value === "string" ? value : String(value);
|
|
2300
|
-
const tag = found.group.tags.find((t) => effectiveGameId(t) === wanted);
|
|
1380
|
+
const tag = found.group.tags.find((t) => (0, import_model.effectiveGameId)(t) === wanted);
|
|
2301
1381
|
if (!tag) {
|
|
2302
1382
|
this.emit({ type: "diagnostic", where, message: `${ref} is "${wanted}", which is not one of the tags of "${groupName}"` });
|
|
2303
1383
|
return;
|
|
2304
1384
|
}
|
|
2305
1385
|
boundTags.set(groupId, tag.id);
|
|
2306
|
-
askNames[groupName] = effectiveGameId(tag);
|
|
1386
|
+
askNames[groupName] = (0, import_model.effectiveGameId)(tag);
|
|
2307
1387
|
}
|
|
2308
1388
|
/**
|
|
2309
1389
|
* Bind every `boundBy` group in the box from the property it names.
|
|
@@ -2323,24 +1403,24 @@ var Flow = class {
|
|
|
2323
1403
|
if (group.boundBy === void 0 || boundTags.has(group.id)) continue;
|
|
2324
1404
|
const ref = /^@(world|story)\.([a-z][a-z0-9_-]*)$/.exec(group.boundBy);
|
|
2325
1405
|
if (!ref) {
|
|
2326
|
-
this.emit({ type: "diagnostic", where: `tag group ${effectiveGameId(group)}`, message: `boundBy "${group.boundBy}" is not a @world or @story property reference` });
|
|
1406
|
+
this.emit({ type: "diagnostic", where: `tag group ${(0, import_model.effectiveGameId)(group)}`, message: `boundBy "${group.boundBy}" is not a @world or @story property reference` });
|
|
2327
1407
|
continue;
|
|
2328
1408
|
}
|
|
2329
1409
|
let value;
|
|
2330
1410
|
try {
|
|
2331
1411
|
value = this.getProperty(`${ref[1]}.${ref[2]}`);
|
|
2332
1412
|
} catch {
|
|
2333
|
-
this.emit({ type: "diagnostic", where: `tag group ${effectiveGameId(group)}`, message: `boundBy "${group.boundBy}" names a property that is not declared` });
|
|
1413
|
+
this.emit({ type: "diagnostic", where: `tag group ${(0, import_model.effectiveGameId)(group)}`, message: `boundBy "${group.boundBy}" names a property that is not declared` });
|
|
2334
1414
|
continue;
|
|
2335
1415
|
}
|
|
2336
1416
|
const wanted = typeof value === "string" ? value : String(value);
|
|
2337
|
-
const tag = group.tags.find((t) => effectiveGameId(t) === wanted);
|
|
1417
|
+
const tag = group.tags.find((t) => (0, import_model.effectiveGameId)(t) === wanted);
|
|
2338
1418
|
if (!tag) {
|
|
2339
|
-
this.emit({ type: "diagnostic", where: `tag group ${effectiveGameId(group)}`, message: `${group.boundBy} is "${wanted}", which is not one of its tags` });
|
|
1419
|
+
this.emit({ type: "diagnostic", where: `tag group ${(0, import_model.effectiveGameId)(group)}`, message: `${group.boundBy} is "${wanted}", which is not one of its tags` });
|
|
2340
1420
|
continue;
|
|
2341
1421
|
}
|
|
2342
1422
|
boundTags.set(group.id, tag.id);
|
|
2343
|
-
askNames[effectiveGameId(group)] = effectiveGameId(tag);
|
|
1423
|
+
askNames[(0, import_model.effectiveGameId)(group)] = (0, import_model.effectiveGameId)(tag);
|
|
2344
1424
|
}
|
|
2345
1425
|
}
|
|
2346
1426
|
// --- @hand composition (schema 3.6) -------------------------------------------
|
|
@@ -2357,7 +1437,7 @@ var Flow = class {
|
|
|
2357
1437
|
const bag = {};
|
|
2358
1438
|
const sources = /* @__PURE__ */ new Map();
|
|
2359
1439
|
for (const [groupId, tagId] of ask.boundTags) {
|
|
2360
|
-
if (groupId === PLACE_GROUP) continue;
|
|
1440
|
+
if (groupId === import_model.PLACE_GROUP) continue;
|
|
2361
1441
|
for (const [name, value] of Object.entries(this.valuesOf("value", tagId))) {
|
|
2362
1442
|
bag[name] = value;
|
|
2363
1443
|
sources.set(name, { kind: "value", id: tagId });
|
|
@@ -2412,13 +1492,13 @@ var Flow = class {
|
|
|
2412
1492
|
* the bound tag or omits the group (wildcard); the home group inverts -
|
|
2413
1493
|
* a homed card requires a matching home binding (schema 2.4). */
|
|
2414
1494
|
tagsMatch(card, boundTags) {
|
|
2415
|
-
const home = card.tags?.[PLACE_GROUP];
|
|
1495
|
+
const home = card.tags?.[import_model.PLACE_GROUP];
|
|
2416
1496
|
if (home !== void 0 && home.length > 0) {
|
|
2417
|
-
const bound = boundTags.get(PLACE_GROUP);
|
|
1497
|
+
const bound = boundTags.get(import_model.PLACE_GROUP);
|
|
2418
1498
|
if (bound === void 0 || !home.includes(bound)) return false;
|
|
2419
1499
|
}
|
|
2420
1500
|
for (const [groupId, tagId] of boundTags) {
|
|
2421
|
-
if (groupId === PLACE_GROUP) continue;
|
|
1501
|
+
if (groupId === import_model.PLACE_GROUP) continue;
|
|
2422
1502
|
const tags = card.tags?.[groupId];
|
|
2423
1503
|
if (tags === void 0) {
|
|
2424
1504
|
if (this.internals.requiredGroups.has(groupId)) return false;
|
|
@@ -2436,9 +1516,9 @@ var Flow = class {
|
|
|
2436
1516
|
const { box } = ask;
|
|
2437
1517
|
const handEnv = this.buildHandEnv(ask);
|
|
2438
1518
|
const verdict = (card, v) => {
|
|
2439
|
-
trace?.push({ id: effectiveGameId(card), verdict: v });
|
|
1519
|
+
trace?.push({ id: (0, import_model.effectiveGameId)(card), verdict: v });
|
|
2440
1520
|
};
|
|
2441
|
-
if (!this.passes(ask.condition, this.evalCtx(box, void 0, handEnv), `hand ${ask.hand ? effectiveGameId(ask.hand) : ""} condition`)) {
|
|
1521
|
+
if (!this.passes(ask.condition, this.evalCtx(box, void 0, handEnv), `hand ${ask.hand ? (0, import_model.effectiveGameId)(ask.hand) : ""} condition`)) {
|
|
2442
1522
|
return { ordered: [], handEnv };
|
|
2443
1523
|
}
|
|
2444
1524
|
const gateOk = /* @__PURE__ */ new Map();
|
|
@@ -2503,9 +1583,9 @@ var Flow = class {
|
|
|
2503
1583
|
let spec = 0;
|
|
2504
1584
|
if (box.ranking.specificity && card.condition) {
|
|
2505
1585
|
const node = this.node(card.condition);
|
|
2506
|
-
spec = matchedSpecificity(node, (n) => {
|
|
1586
|
+
spec = (0, import_expr_specificity.matchedSpecificity)(node, (n) => {
|
|
2507
1587
|
try {
|
|
2508
|
-
return conditionPasses(evaluate(n, deckCtx, storyletsDialect));
|
|
1588
|
+
return conditionPasses((0, import_expr2.evaluate)(n, deckCtx, import_dialect.storyletsDialect));
|
|
2509
1589
|
} catch {
|
|
2510
1590
|
return false;
|
|
2511
1591
|
}
|
|
@@ -2521,13 +1601,13 @@ var Flow = class {
|
|
|
2521
1601
|
while (j < scored.length && scored[j].priority === scored[i].priority && scored[j].spec === scored[i].spec) j++;
|
|
2522
1602
|
if (j - i > 1) {
|
|
2523
1603
|
const run = scored.slice(i, j);
|
|
2524
|
-
shuffleInPlace(run, this.prng);
|
|
1604
|
+
(0, import_expr.shuffleInPlace)(run, this.prng);
|
|
2525
1605
|
for (let k = 0; k < run.length; k++) scored[i + k] = run[k];
|
|
2526
1606
|
}
|
|
2527
1607
|
i = j;
|
|
2528
1608
|
}
|
|
2529
1609
|
for (const s of scored) {
|
|
2530
|
-
trace?.push({ id: effectiveGameId(s.entry.card), verdict: "dealt", priority: s.priority, specificity: s.spec });
|
|
1610
|
+
trace?.push({ id: (0, import_model.effectiveGameId)(s.entry.card), verdict: "dealt", priority: s.priority, specificity: s.spec });
|
|
2531
1611
|
}
|
|
2532
1612
|
return { ordered: scored.map((s) => s.entry), handEnv };
|
|
2533
1613
|
}
|
|
@@ -2543,7 +1623,7 @@ var Flow = class {
|
|
|
2543
1623
|
const { card } = entry;
|
|
2544
1624
|
return {
|
|
2545
1625
|
id: card.id,
|
|
2546
|
-
gameId: effectiveGameId(card),
|
|
1626
|
+
gameId: (0, import_model.effectiveGameId)(card),
|
|
2547
1627
|
...card.title !== void 0 ? { title: card.title } : {},
|
|
2548
1628
|
...card.purpose !== void 0 ? { purpose: card.purpose } : {},
|
|
2549
1629
|
...card.fields !== void 0 ? { fields: card.fields } : {}
|
|
@@ -2574,16 +1654,16 @@ var Flow = class {
|
|
|
2574
1654
|
const { ordered } = this.runAsk(ask, (card, shared) => this.claimVerdict(card, shared, claimCounts, worldClaims), trace);
|
|
2575
1655
|
const listed = n === void 0 ? ordered : ordered.slice(0, Math.max(n, 0));
|
|
2576
1656
|
if (trace) {
|
|
2577
|
-
this.capTrace(trace, new Set(listed.map((e) => effectiveGameId(e.card))));
|
|
2578
|
-
this.emit({ type: "peek", box: effectiveGameId(box), criteria, cards: trace }, this.turnCounts.get(box.id) ?? 0);
|
|
1657
|
+
this.capTrace(trace, new Set(listed.map((e) => (0, import_model.effectiveGameId)(e.card))));
|
|
1658
|
+
this.emit({ type: "peek", box: (0, import_model.effectiveGameId)(box), criteria, cards: trace }, this.turnCounts.get(box.id) ?? 0);
|
|
2579
1659
|
}
|
|
2580
|
-
return { box: effectiveGameId(box), cards: listed.map((e) => this.view(e)) };
|
|
1660
|
+
return { box: (0, import_model.effectiveGameId)(box), cards: listed.map((e) => this.view(e)) };
|
|
2581
1661
|
}
|
|
2582
1662
|
/** Refresh one hand (schema 3.5); returns its new shape. */
|
|
2583
1663
|
deal(handRef) {
|
|
2584
1664
|
this.assertOpen();
|
|
2585
1665
|
const { hand } = this.resolveHand(handRef);
|
|
2586
|
-
return this.dealMany([handRef])[effectiveGameId(hand)] ?? [];
|
|
1666
|
+
return this.dealMany([handRef])[(0, import_model.effectiveGameId)(hand)] ?? [];
|
|
2587
1667
|
}
|
|
2588
1668
|
/** Re-deal several / all hands (schema 3.5): seeded hand-order shuffle
|
|
2589
1669
|
* (fairness), evict, seed the ledger from survivors, fill in order.
|
|
@@ -2592,7 +1672,7 @@ var Flow = class {
|
|
|
2592
1672
|
dealMany(handRefs) {
|
|
2593
1673
|
this.assertOpen();
|
|
2594
1674
|
const dealt = (handRefs ?? [...this.internals.handsById.keys()].sort()).map((ref) => this.resolveHand(ref));
|
|
2595
|
-
shuffleInPlace(dealt, this.prng);
|
|
1675
|
+
(0, import_expr.shuffleInPlace)(dealt, this.prng);
|
|
2596
1676
|
for (const { hand, box } of dealt) {
|
|
2597
1677
|
const ask = this.askForHand(hand, box);
|
|
2598
1678
|
const handEnv = this.buildHandEnv(ask);
|
|
@@ -2605,7 +1685,7 @@ var Flow = class {
|
|
|
2605
1685
|
const evicted = [];
|
|
2606
1686
|
const evict = (cardId, reason) => {
|
|
2607
1687
|
const known = this.internals.cardsById.get(cardId);
|
|
2608
|
-
evicted.push({ card: known ? effectiveGameId(known.card) : cardId, reason });
|
|
1688
|
+
evicted.push({ card: known ? (0, import_model.effectiveGameId)(known.card) : cardId, reason });
|
|
2609
1689
|
return false;
|
|
2610
1690
|
};
|
|
2611
1691
|
const survivors = (this.boardContents.get(hand.id) ?? []).filter((cardId) => {
|
|
@@ -2622,7 +1702,7 @@ var Flow = class {
|
|
|
2622
1702
|
});
|
|
2623
1703
|
this.boardContents.set(hand.id, survivors);
|
|
2624
1704
|
if (this.tracing) {
|
|
2625
|
-
for (const e of evicted) this.emit({ type: "evict", hand: effectiveGameId(hand), card: e.card, reason: e.reason }, turn);
|
|
1705
|
+
for (const e of evicted) this.emit({ type: "evict", hand: (0, import_model.effectiveGameId)(hand), card: e.card, reason: e.reason }, turn);
|
|
2626
1706
|
}
|
|
2627
1707
|
}
|
|
2628
1708
|
const claimCounts = this.claims();
|
|
@@ -2647,12 +1727,12 @@ var Flow = class {
|
|
|
2647
1727
|
worldClaims.set(id, (worldClaims.get(id) ?? 0) + 1);
|
|
2648
1728
|
}
|
|
2649
1729
|
if (trace) {
|
|
2650
|
-
this.capTrace(trace, new Set(taking.map((e) => effectiveGameId(e.card))));
|
|
2651
|
-
this.emit({ type: "deal", hand: effectiveGameId(hand), cards: trace }, this.turnCounts.get(box.id) ?? 0);
|
|
1730
|
+
this.capTrace(trace, new Set(taking.map((e) => (0, import_model.effectiveGameId)(e.card))));
|
|
1731
|
+
this.emit({ type: "deal", hand: (0, import_model.effectiveGameId)(hand), cards: trace }, this.turnCounts.get(box.id) ?? 0);
|
|
2652
1732
|
}
|
|
2653
1733
|
}
|
|
2654
1734
|
return Object.fromEntries(dealt.map(({ hand }) => [
|
|
2655
|
-
effectiveGameId(hand),
|
|
1735
|
+
(0, import_model.effectiveGameId)(hand),
|
|
2656
1736
|
(this.boardContents.get(hand.id) ?? []).map((id) => this.view(this.internals.cardsById.get(id)))
|
|
2657
1737
|
]));
|
|
2658
1738
|
}
|
|
@@ -2674,7 +1754,7 @@ var Flow = class {
|
|
|
2674
1754
|
keep = box.id;
|
|
2675
1755
|
}
|
|
2676
1756
|
return Object.fromEntries([...this.boardContents.entries()].filter(([handId]) => keep === void 0 || this.internals.handsById.get(handId).box.id === keep).map(([handId, ids]) => [
|
|
2677
|
-
effectiveGameId(this.internals.handsById.get(handId).hand),
|
|
1757
|
+
(0, import_model.effectiveGameId)(this.internals.handsById.get(handId).hand),
|
|
2678
1758
|
ids.map((id) => this.view(this.internals.cardsById.get(id)))
|
|
2679
1759
|
]));
|
|
2680
1760
|
}
|
|
@@ -2684,7 +1764,7 @@ var Flow = class {
|
|
|
2684
1764
|
if (!entry) throw new Error(`unknown card "${cardId}"`);
|
|
2685
1765
|
const { hand, box } = this.resolveHand(handRef);
|
|
2686
1766
|
if (!(this.boardContents.get(hand.id) ?? []).includes(entry.card.id)) {
|
|
2687
|
-
throw new Error(`card "${effectiveGameId(entry.card)}" is not dealt to hand "${effectiveGameId(hand)}"`);
|
|
1767
|
+
throw new Error(`card "${(0, import_model.effectiveGameId)(entry.card)}" is not dealt to hand "${(0, import_model.effectiveGameId)(hand)}"`);
|
|
2688
1768
|
}
|
|
2689
1769
|
return { entry, ask: this.askForHand(hand, box) };
|
|
2690
1770
|
}
|
|
@@ -2696,7 +1776,7 @@ var Flow = class {
|
|
|
2696
1776
|
const ctx = this.evalCtx(entry.box, entry.deck, this.buildHandEnv(ask));
|
|
2697
1777
|
return entry.card.outcomes.map((o) => ({
|
|
2698
1778
|
id: o.id,
|
|
2699
|
-
gameId: effectiveGameId(o),
|
|
1779
|
+
gameId: (0, import_model.effectiveGameId)(o),
|
|
2700
1780
|
...o.title !== void 0 ? { title: o.title } : {},
|
|
2701
1781
|
...o.purpose !== void 0 ? { purpose: o.purpose } : {},
|
|
2702
1782
|
...o.fields !== void 0 ? { fields: o.fields } : {},
|
|
@@ -2720,14 +1800,14 @@ var Flow = class {
|
|
|
2720
1800
|
const { entry, ask } = this.resolveDealt(cardId, from);
|
|
2721
1801
|
const bare = outcomeGameId === "";
|
|
2722
1802
|
if (bare && entry.card.outcomes.length > 0) {
|
|
2723
|
-
throw new Error(`card "${effectiveGameId(entry.card)}" has outcomes (${entry.card.outcomes.map((o) => effectiveGameId(o)).join(", ")}); name the one played`);
|
|
1803
|
+
throw new Error(`card "${(0, import_model.effectiveGameId)(entry.card)}" has outcomes (${entry.card.outcomes.map((o) => (0, import_model.effectiveGameId)(o)).join(", ")}); name the one played`);
|
|
2724
1804
|
}
|
|
2725
|
-
const outcome = bare ? void 0 : entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);
|
|
2726
|
-
if (!bare && !outcome) throw new Error(`card "${effectiveGameId(entry.card)}" has no outcome "${outcomeGameId}"`);
|
|
1805
|
+
const outcome = bare ? void 0 : entry.card.outcomes.find((o) => (0, import_model.effectiveGameId)(o) === outcomeGameId);
|
|
1806
|
+
if (!bare && !outcome) throw new Error(`card "${(0, import_model.effectiveGameId)(entry.card)}" has no outcome "${outcomeGameId}"`);
|
|
2727
1807
|
const handEnv = this.buildHandEnv(ask);
|
|
2728
1808
|
const ctx = this.evalCtx(entry.box, entry.deck, handEnv);
|
|
2729
1809
|
if (outcome && !this.passes(outcome.condition, ctx)) {
|
|
2730
|
-
throw new Error(`outcome "${outcomeGameId}" on "${effectiveGameId(entry.card)}" is gated shut`);
|
|
1810
|
+
throw new Error(`outcome "${outcomeGameId}" on "${(0, import_model.effectiveGameId)(entry.card)}" is gated shut`);
|
|
2731
1811
|
}
|
|
2732
1812
|
const perPlay = entry.box.turn !== void 0 ? 0 : this.internals.bundle.settings.playAdvancesTurns;
|
|
2733
1813
|
const newTurn = (this.turnCounts.get(entry.box.id) ?? 0) + (opts.advanceTurns ?? perPlay);
|
|
@@ -2739,8 +1819,8 @@ var Flow = class {
|
|
|
2739
1819
|
const { path, prev } = this.applyWrite(target, value, entry, handEnv);
|
|
2740
1820
|
if (this.tracing) this.emit({ type: "write", target, path, value, ...prev !== void 0 ? { prev } : {} }, newTurn);
|
|
2741
1821
|
}
|
|
2742
|
-
const outcomeId = outcome ? effectiveGameId(outcome) : "";
|
|
2743
|
-
const record = { card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn };
|
|
1822
|
+
const outcomeId = outcome ? (0, import_model.effectiveGameId)(outcome) : "";
|
|
1823
|
+
const record = { card: (0, import_model.effectiveGameId)(entry.card), outcome: outcomeId, turn: newTurn };
|
|
2744
1824
|
this.playLog.push(record);
|
|
2745
1825
|
this.indexPlay(record);
|
|
2746
1826
|
if (entry.card.redraw === "never") {
|
|
@@ -2755,7 +1835,7 @@ var Flow = class {
|
|
|
2755
1835
|
(this.boardContents.get(handId) ?? []).filter((id) => id !== entry.card.id)
|
|
2756
1836
|
);
|
|
2757
1837
|
this.turnCounts.set(entry.box.id, newTurn);
|
|
2758
|
-
if (this.tracing) this.emit({ type: "play", card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn }, newTurn);
|
|
1838
|
+
if (this.tracing) this.emit({ type: "play", card: (0, import_model.effectiveGameId)(entry.card), outcome: outcomeId, turn: newTurn }, newTurn);
|
|
2759
1839
|
}
|
|
2760
1840
|
/** One owned property's address, owner segment and all (4.4). */
|
|
2761
1841
|
address(kind, id) {
|
|
@@ -2820,7 +1900,7 @@ var Flow = class {
|
|
|
2820
1900
|
if (!box) throw new Error(`unknown box "${boxRef}"`);
|
|
2821
1901
|
const next = (this.turnCounts.get(box.id) ?? 0) + n;
|
|
2822
1902
|
this.turnCounts.set(box.id, next);
|
|
2823
|
-
if (this.tracing) this.emit({ type: "turns", box: effectiveGameId(box), turn: next }, next);
|
|
1903
|
+
if (this.tracing) this.emit({ type: "turns", box: (0, import_model.effectiveGameId)(box), turn: next }, next);
|
|
2824
1904
|
}
|
|
2825
1905
|
// --- state access (host surface + test tooling) ---------------------------------
|
|
2826
1906
|
/** Every box, bundle order: identity + this flow's clock (the enumeration
|
|
@@ -2829,7 +1909,7 @@ var Flow = class {
|
|
|
2829
1909
|
this.assertOpen();
|
|
2830
1910
|
return this.internals.bundle.boxes.map((b) => ({
|
|
2831
1911
|
id: b.id,
|
|
2832
|
-
gameId: effectiveGameId(b),
|
|
1912
|
+
gameId: (0, import_model.effectiveGameId)(b),
|
|
2833
1913
|
...b.title !== void 0 ? { title: b.title } : {},
|
|
2834
1914
|
turn: this.turnCounts.get(b.id) ?? 0
|
|
2835
1915
|
}));
|
|
@@ -2966,7 +2046,7 @@ var Flow = class {
|
|
|
2966
2046
|
for (const [boxId, turn] of Object.entries(saved.turns ?? {})) {
|
|
2967
2047
|
if (this.turnCounts.has(boxId)) this.turnCounts.set(boxId, turn);
|
|
2968
2048
|
}
|
|
2969
|
-
this.prng = makePrng(saved.prng);
|
|
2049
|
+
this.prng = (0, import_expr.makePrng)(saved.prng);
|
|
2970
2050
|
this.cooldowns = saved.cooldowns ?? {};
|
|
2971
2051
|
this.playLog = saved.playLog ?? [];
|
|
2972
2052
|
this.rebuildPlayIndex();
|
|
@@ -2978,6 +2058,7 @@ var Flow = class {
|
|
|
2978
2058
|
};
|
|
2979
2059
|
|
|
2980
2060
|
// src/describe.ts
|
|
2061
|
+
var import_model3 = require("@storylet-studio/model");
|
|
2981
2062
|
var summarise = (decls) => decls.map((d) => ({
|
|
2982
2063
|
name: d.name,
|
|
2983
2064
|
type: d.type,
|
|
@@ -2997,10 +2078,10 @@ var movableHoles = (hand, box) => {
|
|
|
2997
2078
|
const filled = hand.template !== void 0 ? hand.chosen : hand.rule?.bindings;
|
|
2998
2079
|
const out = [];
|
|
2999
2080
|
for (const [groupId, value] of Object.entries(filled ?? {})) {
|
|
3000
|
-
if (!isHoleRef(value)) continue;
|
|
2081
|
+
if (!(0, import_model3.isHoleRef)(value)) continue;
|
|
3001
2082
|
const group = box.tagGroups.find((g) => g.id === groupId);
|
|
3002
2083
|
if (group === void 0) continue;
|
|
3003
|
-
out.push({ group: effectiveGameId(group), from: value });
|
|
2084
|
+
out.push({ group: (0, import_model3.effectiveGameId)(group), from: value });
|
|
3004
2085
|
}
|
|
3005
2086
|
return out;
|
|
3006
2087
|
};
|
|
@@ -3018,7 +2099,7 @@ function describeBundle(bundle) {
|
|
|
3018
2099
|
];
|
|
3019
2100
|
const totals = { boxes: 0, decks: 0, cards: 0, hands: 0, templates: 0, tagGroups: 0 };
|
|
3020
2101
|
for (const box of bundle.boxes) {
|
|
3021
|
-
const boxGameId = effectiveGameId(box);
|
|
2102
|
+
const boxGameId = (0, import_model3.effectiveGameId)(box);
|
|
3022
2103
|
const cards = box.decks.reduce((n, deck) => n + deck.cards.length, 0);
|
|
3023
2104
|
boxes.push({
|
|
3024
2105
|
gameId: boxGameId,
|
|
@@ -3027,8 +2108,8 @@ function describeBundle(bundle) {
|
|
|
3027
2108
|
...box.turn !== void 0 ? { turn: { seconds: box.turn.seconds } } : {},
|
|
3028
2109
|
...durableCardCount(box) > 0 ? { durableCards: durableCardCount(box) } : {},
|
|
3029
2110
|
tagGroups: box.tagGroups.map((group) => ({
|
|
3030
|
-
gameId: effectiveGameId(group),
|
|
3031
|
-
tags: group.tags.map((tag) => effectiveGameId(tag))
|
|
2111
|
+
gameId: (0, import_model3.effectiveGameId)(group),
|
|
2112
|
+
tags: group.tags.map((tag) => (0, import_model3.effectiveGameId)(tag))
|
|
3032
2113
|
})),
|
|
3033
2114
|
counts: {
|
|
3034
2115
|
decks: box.decks.length,
|
|
@@ -3048,11 +2129,11 @@ function describeBundle(bundle) {
|
|
|
3048
2129
|
const template = hand.template !== void 0 ? box.handTemplates.find((t) => t.id === hand.template) : void 0;
|
|
3049
2130
|
const movable = movableHoles(hand, box);
|
|
3050
2131
|
hands.push({
|
|
3051
|
-
gameId: effectiveGameId(hand),
|
|
2132
|
+
gameId: (0, import_model3.effectiveGameId)(hand),
|
|
3052
2133
|
...hand.title !== void 0 ? { title: hand.title } : {},
|
|
3053
2134
|
box: boxGameId,
|
|
3054
2135
|
slots: handSlots(hand, box),
|
|
3055
|
-
...template !== void 0 ? { template: effectiveGameId(template) } : {},
|
|
2136
|
+
...template !== void 0 ? { template: (0, import_model3.effectiveGameId)(template) } : {},
|
|
3056
2137
|
...movable.length > 0 ? { movable } : {}
|
|
3057
2138
|
});
|
|
3058
2139
|
}
|
|
@@ -3067,11 +2148,11 @@ function describeBundle(bundle) {
|
|
|
3067
2148
|
});
|
|
3068
2149
|
};
|
|
3069
2150
|
push("box", boxGameId, box.properties);
|
|
3070
|
-
for (const deck of box.decks) push("deck", effectiveGameId(deck), deck.properties);
|
|
3071
|
-
for (const hand of box.hands) push("hand", effectiveGameId(hand), handDecls(hand, box));
|
|
2151
|
+
for (const deck of box.decks) push("deck", (0, import_model3.effectiveGameId)(deck), deck.properties);
|
|
2152
|
+
for (const hand of box.hands) push("hand", (0, import_model3.effectiveGameId)(hand), handDecls(hand, box));
|
|
3072
2153
|
for (const group of box.tagGroups) {
|
|
3073
2154
|
for (const tag of group.tags) {
|
|
3074
|
-
push("tag", effectiveGameId(tag), tag.properties ?? [], effectiveGameId(group));
|
|
2155
|
+
push("tag", (0, import_model3.effectiveGameId)(tag), tag.properties ?? [], (0, import_model3.effectiveGameId)(group));
|
|
3075
2156
|
}
|
|
3076
2157
|
}
|
|
3077
2158
|
}
|