@patterkit/runtime 0.19.0 → 0.20.0
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/CHANGELOG.md +40 -0
- package/README.md +4 -3
- package/dist/index.cjs +85 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -18
- package/dist/index.d.ts +51 -18
- package/dist/index.js +85 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,46 @@ version number always means the same runtime behaviour. This package is versione
|
|
|
33
33
|
|
|
34
34
|
## [Unreleased]
|
|
35
35
|
|
|
36
|
+
## [0.20.0] - 2026-10-06
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`hostScopes`, the engine option every other runtime takes**: the scopes the game binds, by token, each a `{
|
|
41
|
+
get, set? }` over values it owns, as in `hostScopes: { world: { get, set } }`. A resolver's type is
|
|
42
|
+
`HostScope`.
|
|
43
|
+
- **`engine.buildId`**, as the other runtimes have: the bundle's content hash, the build a debug link handshakes
|
|
44
|
+
with.
|
|
45
|
+
- **A bundle-description section in the conformance corpus.** What `describeBundle` says about one bundle, field
|
|
46
|
+
for field, held on every runtime.
|
|
47
|
+
- **`getVisitCounts()` on the engine and on a flow**, on every runtime: how many times each node has been
|
|
48
|
+
entered, across every flow or in this one, read without a save.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **Less work per step.** A decision's log entry is built only while the log is on or something is tracing, and
|
|
53
|
+
a quality's stage ladder is looked up once per set of declarations rather than scanned for on every
|
|
54
|
+
comparison.
|
|
55
|
+
|
|
56
|
+
### Deprecated
|
|
57
|
+
|
|
58
|
+
- **`flow.start()` is deprecated: use `flow.reset()`.** They were the same call under two names. `flow.reset()`
|
|
59
|
+
is the one public name for beginning a flow again on every runtime (opening a flow begins a new one);
|
|
60
|
+
`flow.start()` goes in a later release.
|
|
61
|
+
- **The `world` option and the `WorldResolver` type: use `hostScopes: { world }` and `HostScope`.** `world` is
|
|
62
|
+
still the world scope, and goes in a later release.
|
|
63
|
+
- **The `TraceHandler` type.** Nothing takes one: `onTrace` takes an `EngineTraceHandler`. It goes in a later
|
|
64
|
+
release.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **`reset()` no longer replays a prompt from the run it abandoned.** With prompt replay on, `choose()` leaves
|
|
69
|
+
the chosen option's prompt waiting for the next `advance()`. `reset()` in between cleared only the choice, so
|
|
70
|
+
the old prompt played as the restarted run's first beat. Every move that leaves the flow's place (`reset()`,
|
|
71
|
+
`goto`, `close`) now drops both, through one helper.
|
|
72
|
+
- **`onTrace` hears every decision with the log off.** A `run` walk past a snippet whose condition failed, and a
|
|
73
|
+
write's previous value, were only worked out when the log was on, so a tool tracing a run without the log
|
|
74
|
+
missed the one and got a write without the other. They are now worked out whenever anything is tracing.
|
|
75
|
+
|
|
36
76
|
## [0.19.0] - 2026-10-06
|
|
37
77
|
|
|
38
78
|
### Added
|
package/README.md
CHANGED
|
@@ -44,9 +44,10 @@ for (;;) {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
`new Engine(bundle, options)` takes `{ rng?, seed?, locale?,
|
|
48
|
-
- `
|
|
49
|
-
|
|
47
|
+
`new Engine(bundle, options)` takes `{ rng?, seed?, locale?, hostScopes?, registry?, replayPromptOnChoose?, closedCaptions? }`:
|
|
48
|
+
- `hostScopes` binds the scopes your game owns, by token, each `{ get(name), set?(name, value) }`: for World
|
|
49
|
+
Properties, `hostScopes: { world: { get, set } }`. Leave `world` unbound and the runtime self-backs `@world`
|
|
50
|
+
from the declared defaults, as a property it saves.
|
|
50
51
|
- `registry` is the game's one `ScopeRegistry` (from `@wildwinter/scoperegistry`), holding every
|
|
51
52
|
engine's properties. Omit it and the engine makes its own. See *One registry per game* below.
|
|
52
53
|
- `locale` plays a non-default language (embedded localisation; an IDs-only bundle ignores it).
|
package/dist/index.cjs
CHANGED
|
@@ -114,7 +114,7 @@ var Engine = class _Engine {
|
|
|
114
114
|
sceneGameIdToId = /* @__PURE__ */ new Map();
|
|
115
115
|
blockGameIdToId = /* @__PURE__ */ new Map();
|
|
116
116
|
/** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
|
|
117
|
-
* engine keeps the same
|
|
117
|
+
* engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
|
|
118
118
|
creationOptions;
|
|
119
119
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
120
120
|
* unwritten unless `options.log` asked for it. */
|
|
@@ -166,12 +166,12 @@ var Engine = class _Engine {
|
|
|
166
166
|
registry.mountOwned("patter", patterBag, { owner: OWNER });
|
|
167
167
|
registered.push("patter");
|
|
168
168
|
const hostBound = /* @__PURE__ */ new Set();
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const decls = (
|
|
172
|
-
registry.defineForeign(
|
|
173
|
-
registered.push(
|
|
174
|
-
hostBound.add(
|
|
169
|
+
for (const [token, resolver] of Object.entries(boundScopes(options))) {
|
|
170
|
+
const spec = bundle.scopeRegistry?.scopes.find((s) => s.token === token);
|
|
171
|
+
const decls = (spec?.declarations ?? []).map(toForeignDecl);
|
|
172
|
+
registry.defineForeign(token, resolver, decls, { writable: spec?.writable ?? true, owner: OWNER });
|
|
173
|
+
registered.push(token);
|
|
174
|
+
hostBound.add(token);
|
|
175
175
|
}
|
|
176
176
|
for (const spec of ownsRegistry ? bundle.scopeRegistry?.scopes ?? [] : []) {
|
|
177
177
|
if (hostBound.has(spec.token) || registry.has(spec.token)) continue;
|
|
@@ -191,6 +191,7 @@ var Engine = class _Engine {
|
|
|
191
191
|
}
|
|
192
192
|
this.host = {
|
|
193
193
|
logEnabled: options.log ?? false,
|
|
194
|
+
tracing: options.log ?? false,
|
|
194
195
|
emitEngine: (flow, event, scene) => this.emitEngine(flow, event, scene),
|
|
195
196
|
bundle,
|
|
196
197
|
emitIds,
|
|
@@ -213,6 +214,7 @@ var Engine = class _Engine {
|
|
|
213
214
|
patterSharedNames,
|
|
214
215
|
sceneSharedNames,
|
|
215
216
|
sharedVisits: /* @__PURE__ */ new Map(),
|
|
217
|
+
qualityLadders: /* @__PURE__ */ new Map(),
|
|
216
218
|
sharedSelectors: /* @__PURE__ */ new Map(),
|
|
217
219
|
stageBags: /* @__PURE__ */ new Map(),
|
|
218
220
|
journal: null,
|
|
@@ -239,6 +241,11 @@ var Engine = class _Engine {
|
|
|
239
241
|
get isSourceDebug() {
|
|
240
242
|
return this.sourceDebug;
|
|
241
243
|
}
|
|
244
|
+
/** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty
|
|
245
|
+
* string for a bundle compiled without one. */
|
|
246
|
+
get buildId() {
|
|
247
|
+
return this.host.bundle.content?.hash ?? "";
|
|
248
|
+
}
|
|
242
249
|
/**
|
|
243
250
|
* Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
|
|
244
251
|
* string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
|
|
@@ -359,7 +366,7 @@ var Engine = class _Engine {
|
|
|
359
366
|
});
|
|
360
367
|
}
|
|
361
368
|
this.flowsById.set(id, flow);
|
|
362
|
-
flow.
|
|
369
|
+
flow.begin(sceneId, blockId);
|
|
363
370
|
return flow;
|
|
364
371
|
}
|
|
365
372
|
/**
|
|
@@ -723,7 +730,7 @@ var Engine = class _Engine {
|
|
|
723
730
|
for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep });
|
|
724
731
|
this.host.stageBags.clear();
|
|
725
732
|
for (const t of ["patter", ...this.host.hostScopes]) if (reg.has(t)) reg.remove(t, { keep });
|
|
726
|
-
|
|
733
|
+
for (const t of Object.keys(boundScopes(this.creationOptions))) if (reg.has(t)) reg.remove(t);
|
|
727
734
|
}
|
|
728
735
|
/** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
|
|
729
736
|
getProperty(ref) {
|
|
@@ -760,13 +767,22 @@ var Engine = class _Engine {
|
|
|
760
767
|
* retained. Returns its own unsubscribe. */
|
|
761
768
|
onTrace(handler) {
|
|
762
769
|
this.engineTraceHandlers.add(handler);
|
|
763
|
-
|
|
770
|
+
this.host.tracing = true;
|
|
771
|
+
return () => {
|
|
772
|
+
this.engineTraceHandlers.delete(handler);
|
|
773
|
+
this.host.tracing = this.host.logEnabled || this.engineTraceHandlers.size > 0;
|
|
774
|
+
};
|
|
764
775
|
}
|
|
765
776
|
emitEngine(flow, event, scene) {
|
|
766
777
|
for (const h of this.engineTraceHandlers) h(flow, event);
|
|
767
778
|
if (!this.host.logEnabled) return;
|
|
768
779
|
this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...scene ? { scene } : {} });
|
|
769
780
|
}
|
|
781
|
+
/** How many times each node has been entered across every flow, by node id: the shared count. With a
|
|
782
|
+
* flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
|
|
783
|
+
getVisitCounts() {
|
|
784
|
+
return Object.fromEntries(this.host.sharedVisits);
|
|
785
|
+
}
|
|
770
786
|
listProperties() {
|
|
771
787
|
return this.host.patterSharedDecls.map((d) => ({
|
|
772
788
|
name: d.name,
|
|
@@ -971,19 +987,21 @@ var Flow = class {
|
|
|
971
987
|
* lowercase, as the compiler emits references (the selfBackedResolver lesson). */
|
|
972
988
|
stagesFor(scope, name) {
|
|
973
989
|
const key = name.toLowerCase();
|
|
974
|
-
const fromDecls = (decls) => decls?.find((d) => d.name.toLowerCase() === key && d.type === "quality")?.stages;
|
|
975
990
|
if (scope === "patter") {
|
|
976
|
-
return
|
|
991
|
+
return ladders(this.host, "patter", () => [...this.host.patterSharedDecls, ...this.host.patterLocalDecls]).get(key);
|
|
977
992
|
}
|
|
978
993
|
if (scope === "scene") {
|
|
979
|
-
const
|
|
980
|
-
|
|
994
|
+
const sceneId = this.currentSceneId;
|
|
995
|
+
if (sceneId == null) return void 0;
|
|
996
|
+
return ladders(this.host, `scene/${sceneId}`, () => this.host.bundle.scenes[sceneId]?.sceneProps).get(key);
|
|
981
997
|
}
|
|
982
|
-
return this.registryQualities?.(scope, name) ??
|
|
998
|
+
return this.registryQualities?.(scope, name) ?? ladders(this.host, `host/${scope}`, () => this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations).get(key);
|
|
983
999
|
}
|
|
984
1000
|
// -- Host API -------------------------------------------------------------
|
|
985
|
-
/** Begin this flow at a scene (and optionally a specific block within it).
|
|
986
|
-
|
|
1001
|
+
/** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:
|
|
1002
|
+
* openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.
|
|
1003
|
+
* @internal */
|
|
1004
|
+
begin(sceneId, blockId) {
|
|
987
1005
|
if (this.host.journal && !this.host.journal.opened.has(this)) throw new Error("a flow can't be started or reset while a checkpoint is open");
|
|
988
1006
|
this.releaseBags(false);
|
|
989
1007
|
this.host.registry.discardParked(keys.flow(this.id));
|
|
@@ -995,7 +1013,7 @@ var Flow = class {
|
|
|
995
1013
|
this.flowEnded = false;
|
|
996
1014
|
this.activeSnippet = null;
|
|
997
1015
|
this.beatIndex = 0;
|
|
998
|
-
this.
|
|
1016
|
+
this.clearPending();
|
|
999
1017
|
this.started = true;
|
|
1000
1018
|
if (blockId) {
|
|
1001
1019
|
const loc = this.host.blockIndex.get(blockId);
|
|
@@ -1020,10 +1038,15 @@ var Flow = class {
|
|
|
1020
1038
|
* Forget everything in this flow and begin again - its per-flow state (not-shared
|
|
1021
1039
|
* `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
|
|
1022
1040
|
* visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
|
|
1023
|
-
* untouched.
|
|
1041
|
+
* untouched. The one public way to begin a flow again (openFlow begins a new one).
|
|
1024
1042
|
*/
|
|
1025
1043
|
reset(sceneId, blockId) {
|
|
1026
|
-
this.
|
|
1044
|
+
this.begin(sceneId, blockId);
|
|
1045
|
+
}
|
|
1046
|
+
/** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`
|
|
1047
|
+
* was the same call under a second name, and goes in a later release. */
|
|
1048
|
+
start(sceneId, blockId) {
|
|
1049
|
+
this.reset(sceneId, blockId);
|
|
1027
1050
|
}
|
|
1028
1051
|
/**
|
|
1029
1052
|
* Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
|
|
@@ -1047,9 +1070,7 @@ var Flow = class {
|
|
|
1047
1070
|
this.touch();
|
|
1048
1071
|
if (scene === "END") {
|
|
1049
1072
|
this.started = true;
|
|
1050
|
-
this.
|
|
1051
|
-
this.pendingPrompt = null;
|
|
1052
|
-
this.pendingPromptOwnerId = null;
|
|
1073
|
+
this.clearPending();
|
|
1053
1074
|
this.activeSnippet = null;
|
|
1054
1075
|
this.beatIndex = 0;
|
|
1055
1076
|
this.flowEnded = true;
|
|
@@ -1064,12 +1085,10 @@ var Flow = class {
|
|
|
1064
1085
|
if (blockId === void 0) return false;
|
|
1065
1086
|
}
|
|
1066
1087
|
if (!this.started) {
|
|
1067
|
-
this.
|
|
1088
|
+
this.begin(sceneId, blockId);
|
|
1068
1089
|
return true;
|
|
1069
1090
|
}
|
|
1070
|
-
this.
|
|
1071
|
-
this.pendingPrompt = null;
|
|
1072
|
-
this.pendingPromptOwnerId = null;
|
|
1091
|
+
this.clearPending();
|
|
1073
1092
|
this.activeSnippet = null;
|
|
1074
1093
|
this.beatIndex = 0;
|
|
1075
1094
|
this.flowEnded = false;
|
|
@@ -1094,6 +1113,15 @@ var Flow = class {
|
|
|
1094
1113
|
this.stack = [];
|
|
1095
1114
|
this.activeSnippet = null;
|
|
1096
1115
|
this.beatIndex = 0;
|
|
1116
|
+
this.clearPending();
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be
|
|
1120
|
+
* replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none
|
|
1121
|
+
* leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the
|
|
1122
|
+
* next advance() replayed the abandoned run's prompt as the new run's first beat.
|
|
1123
|
+
*/
|
|
1124
|
+
clearPending() {
|
|
1097
1125
|
this.pendingChoice = null;
|
|
1098
1126
|
this.pendingPrompt = null;
|
|
1099
1127
|
this.pendingPromptOwnerId = null;
|
|
@@ -1190,7 +1218,7 @@ var Flow = class {
|
|
|
1190
1218
|
}
|
|
1191
1219
|
const from = frame.index;
|
|
1192
1220
|
while (frame.index < children.length && !this.eligible(children[frame.index])) frame.index++;
|
|
1193
|
-
if (this.host.
|
|
1221
|
+
if (this.host.tracing && frame.index !== from) {
|
|
1194
1222
|
this.emit({
|
|
1195
1223
|
type: "select",
|
|
1196
1224
|
group: frame.containerId,
|
|
@@ -1226,14 +1254,18 @@ var Flow = class {
|
|
|
1226
1254
|
clearLog() {
|
|
1227
1255
|
this.flowLog.length = 0;
|
|
1228
1256
|
}
|
|
1229
|
-
/** Record one decision, on this flow's log and the engine's
|
|
1230
|
-
*
|
|
1257
|
+
/** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call
|
|
1258
|
+
* site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */
|
|
1231
1259
|
emit(event) {
|
|
1232
1260
|
const scene = this.currentSceneId ?? void 0;
|
|
1233
1261
|
this.host.emitEngine(this.id, event, scene);
|
|
1234
1262
|
if (!this.host.logEnabled) return;
|
|
1235
1263
|
this.flowLog.push({ ...event, seq: this.flowSeq++, ...scene ? { scene } : {} });
|
|
1236
1264
|
}
|
|
1265
|
+
/** How many times this flow has entered each node, by node id. */
|
|
1266
|
+
getVisitCounts() {
|
|
1267
|
+
return Object.fromEntries(this.visitCounts);
|
|
1268
|
+
}
|
|
1237
1269
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
1238
1270
|
getChoices() {
|
|
1239
1271
|
return this.pendingChoice?.options ?? [];
|
|
@@ -1247,7 +1279,7 @@ var Flow = class {
|
|
|
1247
1279
|
if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);
|
|
1248
1280
|
this.touch();
|
|
1249
1281
|
const node = choice.byId.get(id);
|
|
1250
|
-
this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1282
|
+
if (this.host.tracing) this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1251
1283
|
this.pendingChoice = null;
|
|
1252
1284
|
const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : void 0;
|
|
1253
1285
|
this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;
|
|
@@ -1443,7 +1475,7 @@ var Flow = class {
|
|
|
1443
1475
|
byId.set(child.id, child);
|
|
1444
1476
|
}
|
|
1445
1477
|
if (options.some((o) => o.eligible)) {
|
|
1446
|
-
this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
1478
|
+
if (this.host.tracing) this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
1447
1479
|
this.pendingChoice = { groupId: group.id, options, byId };
|
|
1448
1480
|
return;
|
|
1449
1481
|
}
|
|
@@ -1452,7 +1484,7 @@ var Flow = class {
|
|
|
1452
1484
|
this.enterChild(fallback);
|
|
1453
1485
|
return;
|
|
1454
1486
|
}
|
|
1455
|
-
this.emit({ type: "dry", group: group.id });
|
|
1487
|
+
if (this.host.tracing) this.emit({ type: "dry", group: group.id });
|
|
1456
1488
|
this.host.onDryChoice?.(group.id);
|
|
1457
1489
|
}
|
|
1458
1490
|
// -- Jumps (jump / call-return) ----------------------------------------
|
|
@@ -1467,7 +1499,7 @@ var Flow = class {
|
|
|
1467
1499
|
* hard-ends the flow regardless of the callstack.
|
|
1468
1500
|
*/
|
|
1469
1501
|
enterTarget(to, mode) {
|
|
1470
|
-
this.emit({ type: "jump", to, mode });
|
|
1502
|
+
if (this.host.tracing) this.emit({ type: "jump", to, mode });
|
|
1471
1503
|
if (to === "END") {
|
|
1472
1504
|
this.flowEnded = true;
|
|
1473
1505
|
this.stack = [];
|
|
@@ -1504,7 +1536,7 @@ var Flow = class {
|
|
|
1504
1536
|
const order = group.options?.order ?? "sequential";
|
|
1505
1537
|
const exhaust = group.options?.exhaust ?? "once";
|
|
1506
1538
|
const trace = (picked) => {
|
|
1507
|
-
this.emit({
|
|
1539
|
+
if (this.host.tracing) this.emit({
|
|
1508
1540
|
type: "select",
|
|
1509
1541
|
group: group.id,
|
|
1510
1542
|
selector: group.selector ?? "default",
|
|
@@ -1669,9 +1701,9 @@ var Flow = class {
|
|
|
1669
1701
|
let value;
|
|
1670
1702
|
try {
|
|
1671
1703
|
value = this.evalExpr(e.value);
|
|
1672
|
-
const prev = this.host.
|
|
1704
|
+
const prev = this.host.tracing ? this.getProperty(e.target) : void 0;
|
|
1673
1705
|
this.writeProperty(e.target, value, false);
|
|
1674
|
-
this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
1706
|
+
if (this.host.tracing) this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
1675
1707
|
} catch (err) {
|
|
1676
1708
|
this.reportError("effect", owner, e.value, err);
|
|
1677
1709
|
}
|
|
@@ -1692,7 +1724,7 @@ var Flow = class {
|
|
|
1692
1724
|
const message = err instanceof Error ? err.message : String(err);
|
|
1693
1725
|
const source = expr?.src;
|
|
1694
1726
|
this.host.onError({ flow: this.id, kind, node, ...source ? { source } : {}, message });
|
|
1695
|
-
this.emit({ type: "diagnostic", kind, node, ...source ? { source } : {}, message });
|
|
1727
|
+
if (this.host.tracing) this.emit({ type: "diagnostic", kind, node, ...source ? { source } : {}, message });
|
|
1696
1728
|
}
|
|
1697
1729
|
evalExpr(expr) {
|
|
1698
1730
|
return (0, import_expr.evaluate)(this.conditionAst(expr), this.context(), import_dialect.patterDialect);
|
|
@@ -1979,6 +2011,21 @@ function mountClaims(registry, key, bag, ask) {
|
|
|
1979
2011
|
function toDecl(decl) {
|
|
1980
2012
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
|
|
1981
2013
|
}
|
|
2014
|
+
function boundScopes(options) {
|
|
2015
|
+
return { ...options.world ? { world: options.world } : {}, ...options.hostScopes };
|
|
2016
|
+
}
|
|
2017
|
+
function ladders(host, cacheKey, decls) {
|
|
2018
|
+
let found = host.qualityLadders.get(cacheKey);
|
|
2019
|
+
if (!found) {
|
|
2020
|
+
found = /* @__PURE__ */ new Map();
|
|
2021
|
+
for (const d of decls() ?? []) {
|
|
2022
|
+
const k = d.name.toLowerCase();
|
|
2023
|
+
if (d.type === "quality" && d.stages && !found.has(k)) found.set(k, d.stages);
|
|
2024
|
+
}
|
|
2025
|
+
host.qualityLadders.set(cacheKey, found);
|
|
2026
|
+
}
|
|
2027
|
+
return found;
|
|
2028
|
+
}
|
|
1982
2029
|
function toForeignDecl(decl) {
|
|
1983
2030
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };
|
|
1984
2031
|
}
|