@patterkit/runtime 0.21.0 → 0.23.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 +25 -0
- package/README.md +9 -2
- package/dist/index.cjs +133 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +111 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,31 @@ version number always means the same runtime behaviour. This package is versione
|
|
|
33
33
|
|
|
34
34
|
## [Unreleased]
|
|
35
35
|
|
|
36
|
+
## [0.23.0] - 2026-10-10
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **Line padding**: the writer's pause after a line, in seconds. A `line` or `text` step carries `padAfter`,
|
|
41
|
+
resolved from the beat's own value, else the nearest `padAfterDefault` above it (its snippet, each group
|
|
42
|
+
around it, innermost first, its block, its scene), else the project's, else the built-in 0.6. Negative is a
|
|
43
|
+
cut-in, the next line (or game event) starting that long before this one ends; it is clamped to zero only on
|
|
44
|
+
a snippet's last line or text beat, where nothing certain follows. A choice option's `prompt` carries its
|
|
45
|
+
`padAfter` too, resolved through its option and never clamped, for a game that voices the prompts itself.
|
|
46
|
+
The outline and beat sequence carry `padAfter` and, when the beat sets its own, `ownPadAfter`.
|
|
47
|
+
`DEFAULT_PAD_AFTER` (0.6) is exported. What to do with it is the game's: the docs give the timing
|
|
48
|
+
rules a writer expects.
|
|
49
|
+
|
|
50
|
+
## [0.22.0] - 2026-10-10
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **Speaker qualifiers**: a line can say how it is delivered, a screenplay's `TAM (O.S.)`, without becoming a
|
|
55
|
+
second character. A `line` step, and a line-kind choice prompt, carries `qualifier`, the qualifier's `gameId`
|
|
56
|
+
(`vo`, `os`, `radio`, or the project's own), and `qualifierName`, its shown name, resolved as `characterName`
|
|
57
|
+
is: the active locale's `qualifier:<gameId>` string, then the default locale's, then the authored name.
|
|
58
|
+
`qualifierName` is absent in an IDs-only build, and both are dropped on a line silenced by closed captions.
|
|
59
|
+
Saves and the outline and beat sequence carry them too. What a qualifier means is the game's to decide.
|
|
60
|
+
|
|
36
61
|
## [0.21.0] - 2026-10-07
|
|
37
62
|
|
|
38
63
|
### Changed
|
package/README.md
CHANGED
|
@@ -44,6 +44,13 @@ for (;;) {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
A `line` step carries `id`, `text`, `character`, `characterName`, `direction`, and, when the line has a
|
|
48
|
+
speaker qualifier, `qualifier` (its `gameId`, `os`) and `qualifierName` (its shown name, `O.S.`). Line and
|
|
49
|
+
text steps both carry `padAfter`, the writer's pause after the beat in seconds, already resolved; a negative
|
|
50
|
+
one means the next line cuts in. Line, text, and game event steps can carry `gameData` and `tags`. The
|
|
51
|
+
[step shapes](https://patterkit.dev/play/engine/#step-shapes) give the full list and the timing rules a game
|
|
52
|
+
should follow.
|
|
53
|
+
|
|
47
54
|
`new Engine(bundle, options)` takes `{ rng?, seed?, locale?, hostScopes?, registry?, replayPromptOnChoose?, closedCaptions? }`:
|
|
48
55
|
- `hostScopes` binds the scopes your game owns, by token, each `{ get(name), set?(name, value) }`: for World
|
|
49
56
|
Properties, `hostScopes: { world: { get, set } }`. Leave `world` unbound and the runtime self-backs `@world`
|
|
@@ -60,8 +67,8 @@ state, visit counts, and PRNG are untouched - so a game's "language" setting can
|
|
|
60
67
|
rebuilding the engine or losing the player's place.
|
|
61
68
|
|
|
62
69
|
The above is the **Embedded** build (strings ship inside the `.patterc`). An **IDs-only** build ships no
|
|
63
|
-
strings: `step.text` is the beat **ID
|
|
64
|
-
its own system, then applies `{@ref}` replacement with **`flow.interpolate(yourString)`**. See the
|
|
70
|
+
strings: `step.text` is the beat **ID**, and `step.characterName` and `step.qualifierName` are omitted, so
|
|
71
|
+
your game localises the IDs in its own system, then applies `{@ref}` replacement with **`flow.interpolate(yourString)`**. See the
|
|
65
72
|
[Localisation guide](https://patterkit.dev/play/localisation/) for both modes across all four runtimes.
|
|
66
73
|
|
|
67
74
|
## Drop-in (`<script>`)
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Checkpoint: () => Checkpoint,
|
|
24
|
+
DEFAULT_PAD_AFTER: () => import_model5.DEFAULT_PAD_AFTER,
|
|
24
25
|
Engine: () => Engine,
|
|
25
26
|
Flow: () => Flow,
|
|
26
27
|
buildTagIndex: () => buildTagIndex,
|
|
@@ -37,7 +38,7 @@ var import_expr_specificity = require("@wildwinter/expr-specificity");
|
|
|
37
38
|
var import_scoperegistry = require("@wildwinter/scoperegistry");
|
|
38
39
|
var import_scoperegistry2 = require("@wildwinter/scoperegistry");
|
|
39
40
|
var import_dialect = require("@patterkit/dialect");
|
|
40
|
-
var
|
|
41
|
+
var import_model2 = require("@patterkit/model");
|
|
41
42
|
|
|
42
43
|
// src/tags.ts
|
|
43
44
|
function dedupe(tags) {
|
|
@@ -73,8 +74,47 @@ function buildTagIndex(bundle) {
|
|
|
73
74
|
return index;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// src/pads.ts
|
|
78
|
+
var import_model = require("@patterkit/model");
|
|
79
|
+
var spoken = (b) => b.kind === "line" || b.kind === "text";
|
|
80
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
81
|
+
function buildPadIndex(bundle) {
|
|
82
|
+
const index = /* @__PURE__ */ new Map();
|
|
83
|
+
const set = (beat, inherited, clamp) => {
|
|
84
|
+
const own = beat.kind === "gameEvent" ? void 0 : num(beat.padAfter);
|
|
85
|
+
let resolved = own ?? inherited;
|
|
86
|
+
if (clamp && resolved < 0) resolved = 0;
|
|
87
|
+
index.set(beat.id, own !== void 0 ? { own, resolved } : { resolved });
|
|
88
|
+
};
|
|
89
|
+
const visit = (node, inherited) => {
|
|
90
|
+
const def = num(node.padAfterDefault) ?? inherited;
|
|
91
|
+
if (node.type === "group") {
|
|
92
|
+
if (node.prompt) set(node.prompt, def, false);
|
|
93
|
+
for (const child of node.children) visit(child, def);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const beats = node.beats ?? [];
|
|
97
|
+
let last = -1;
|
|
98
|
+
beats.forEach((b, i) => {
|
|
99
|
+
if (spoken(b)) last = i;
|
|
100
|
+
});
|
|
101
|
+
beats.forEach((b, i) => {
|
|
102
|
+
if (spoken(b)) set(b, def, i === last);
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const projectDefault = num(bundle.padAfterDefault) ?? import_model.DEFAULT_PAD_AFTER;
|
|
106
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
107
|
+
const sceneDefault = num(scene.padAfterDefault) ?? projectDefault;
|
|
108
|
+
for (const block of scene.blocks) {
|
|
109
|
+
const blockDefault = num(block.padAfterDefault) ?? sceneDefault;
|
|
110
|
+
for (const child of block.children) visit(child, blockDefault);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
115
|
+
|
|
76
116
|
// src/engine.ts
|
|
77
|
-
var
|
|
117
|
+
var import_model3 = require("@patterkit/model");
|
|
78
118
|
var astCache = /* @__PURE__ */ new WeakMap();
|
|
79
119
|
var tagIndexCache = /* @__PURE__ */ new WeakMap();
|
|
80
120
|
function tagIndexFor(bundle) {
|
|
@@ -85,6 +125,15 @@ function tagIndexFor(bundle) {
|
|
|
85
125
|
}
|
|
86
126
|
return index;
|
|
87
127
|
}
|
|
128
|
+
var padIndexCache = /* @__PURE__ */ new WeakMap();
|
|
129
|
+
function padIndexFor(bundle) {
|
|
130
|
+
let index = padIndexCache.get(bundle);
|
|
131
|
+
if (!index) {
|
|
132
|
+
index = buildPadIndex(bundle);
|
|
133
|
+
padIndexCache.set(bundle, index);
|
|
134
|
+
}
|
|
135
|
+
return index;
|
|
136
|
+
}
|
|
88
137
|
function tagsOf(host, id) {
|
|
89
138
|
return host.tagIndex.get(id)?.slice();
|
|
90
139
|
}
|
|
@@ -150,18 +199,20 @@ var Engine = class _Engine {
|
|
|
150
199
|
}
|
|
151
200
|
const castDisplay = /* @__PURE__ */ new Map();
|
|
152
201
|
for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);
|
|
202
|
+
const qualifierDisplay = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const q of bundle.qualifiers ?? []) if (q?.gameId) qualifierDisplay.set(q.gameId, q.name);
|
|
153
204
|
this.defaultSeed = (options.seed ?? 2654435769) >>> 0;
|
|
154
205
|
const nodeIndex = /* @__PURE__ */ new Map();
|
|
155
206
|
const blockIndex = /* @__PURE__ */ new Map();
|
|
156
207
|
const blockById = /* @__PURE__ */ new Map();
|
|
157
208
|
for (const [sceneId, scene] of Object.entries(bundle.scenes)) {
|
|
158
|
-
this.sceneGameIdToId.set((0,
|
|
209
|
+
this.sceneGameIdToId.set((0, import_model2.effectiveGameId)(scene), sceneId);
|
|
159
210
|
const blockAddrs = /* @__PURE__ */ new Map();
|
|
160
211
|
for (const block of scene.blocks) {
|
|
161
212
|
blockIndex.set(block.id, { sceneId });
|
|
162
213
|
blockById.set(block.id, block);
|
|
163
|
-
blockAddrs.set((0,
|
|
164
|
-
(0,
|
|
214
|
+
blockAddrs.set((0, import_model2.effectiveGameId)(block), block.id);
|
|
215
|
+
(0, import_model2.walkNodes)(block.children, (n) => nodeIndex.set(n.id, n));
|
|
165
216
|
}
|
|
166
217
|
this.blockGameIdToId.set(sceneId, blockAddrs);
|
|
167
218
|
}
|
|
@@ -210,6 +261,7 @@ var Engine = class _Engine {
|
|
|
210
261
|
strings,
|
|
211
262
|
defaultStrings,
|
|
212
263
|
castDisplay,
|
|
264
|
+
qualifierDisplay,
|
|
213
265
|
nodeIndex,
|
|
214
266
|
blockIndex,
|
|
215
267
|
blockById,
|
|
@@ -217,6 +269,7 @@ var Engine = class _Engine {
|
|
|
217
269
|
blockGameIdToId: this.blockGameIdToId,
|
|
218
270
|
// same instances the engine resolves with
|
|
219
271
|
tagIndex: tagIndexFor(bundle),
|
|
272
|
+
padIndex: padIndexFor(bundle),
|
|
220
273
|
registry,
|
|
221
274
|
ownsRegistry,
|
|
222
275
|
patterBag,
|
|
@@ -236,9 +289,9 @@ var Engine = class _Engine {
|
|
|
236
289
|
replayPromptOnChoose: options.replayPromptOnChoose ?? false,
|
|
237
290
|
captionsOn: options.closedCaptions ?? true,
|
|
238
291
|
// captions shown by default (full text)
|
|
239
|
-
captionOpen: (bundle.closedCaptions ??
|
|
240
|
-
captionClose: (bundle.closedCaptions ??
|
|
241
|
-
captionCharacter: bundle.closedCaptions?.character ||
|
|
292
|
+
captionOpen: (bundle.closedCaptions ?? import_model2.DEFAULT_CAPTION_DELIMITERS).open,
|
|
293
|
+
captionClose: (bundle.closedCaptions ?? import_model2.DEFAULT_CAPTION_DELIMITERS).close,
|
|
294
|
+
captionCharacter: bundle.closedCaptions?.character || import_model2.DEFAULT_CAPTION_CHARACTER,
|
|
242
295
|
// absent/empty -> SFX
|
|
243
296
|
refSplitCache: /* @__PURE__ */ new Map(),
|
|
244
297
|
refSplitRevision: registry.revision
|
|
@@ -463,11 +516,11 @@ var Engine = class _Engine {
|
|
|
463
516
|
* The inverse of the resolve helpers - for a host that wants to display / log the address. */
|
|
464
517
|
sceneAddress(sceneId) {
|
|
465
518
|
const scene = this.host.bundle.scenes[sceneId];
|
|
466
|
-
return scene ? (0,
|
|
519
|
+
return scene ? (0, import_model2.effectiveGameId)(scene) : void 0;
|
|
467
520
|
}
|
|
468
521
|
blockAddress(blockId) {
|
|
469
522
|
const block = this.host.blockById.get(blockId);
|
|
470
|
-
return block ? (0,
|
|
523
|
+
return block ? (0, import_model2.effectiveGameId)(block) : void 0;
|
|
471
524
|
}
|
|
472
525
|
/**
|
|
473
526
|
* Author tags (#215) accumulated for a beat by id: its own tags unioned with every ancestor's
|
|
@@ -550,13 +603,13 @@ var Engine = class _Engine {
|
|
|
550
603
|
getOutline() {
|
|
551
604
|
return Object.values(this.host.bundle.scenes).map((scene) => ({
|
|
552
605
|
id: scene.id,
|
|
553
|
-
...(0,
|
|
606
|
+
...(0, import_model2.effectiveGameId)(scene) ? { gameId: (0, import_model2.effectiveGameId)(scene) } : {},
|
|
554
607
|
name: scene.name,
|
|
555
608
|
...gameDataField(scene.gameData),
|
|
556
609
|
...this.tagsField(scene.id),
|
|
557
610
|
blocks: scene.blocks.map((block) => ({
|
|
558
611
|
id: block.id,
|
|
559
|
-
...(0,
|
|
612
|
+
...(0, import_model2.effectiveGameId)(block) ? { gameId: (0, import_model2.effectiveGameId)(block) } : {},
|
|
560
613
|
name: block.name,
|
|
561
614
|
...gameDataField(block.gameData),
|
|
562
615
|
...this.tagsField(block.id),
|
|
@@ -573,7 +626,7 @@ var Engine = class _Engine {
|
|
|
573
626
|
const out = [];
|
|
574
627
|
for (const scene of Object.values(this.host.bundle.scenes)) {
|
|
575
628
|
for (const block of scene.blocks) {
|
|
576
|
-
(0,
|
|
629
|
+
(0, import_model2.walkNodes)(block.children, (n) => {
|
|
577
630
|
if (n.type !== "snippet") return;
|
|
578
631
|
for (const beat of n.beats ?? []) {
|
|
579
632
|
out.push({ sceneId: scene.id, blockId: block.id, snippetId: n.id, beat: this.beatInfo(beat) });
|
|
@@ -610,12 +663,20 @@ var Engine = class _Engine {
|
|
|
610
663
|
if (beat.kind === "line") {
|
|
611
664
|
if (beat.character !== void 0) {
|
|
612
665
|
info.character = beat.character;
|
|
613
|
-
const name = this.host.defaultStrings[(0,
|
|
666
|
+
const name = this.host.defaultStrings[(0, import_model2.castStringKey)(beat.character)] ?? this.host.castDisplay.get(beat.character);
|
|
614
667
|
if (name !== void 0) info.characterName = name;
|
|
615
668
|
}
|
|
616
669
|
if (beat.direction !== void 0) info.direction = beat.direction;
|
|
670
|
+
if (beat.qualifier !== void 0) {
|
|
671
|
+
info.qualifier = beat.qualifier;
|
|
672
|
+
const name = this.host.defaultStrings[(0, import_model2.qualifierStringKey)(beat.qualifier)] ?? this.host.qualifierDisplay.get(beat.qualifier);
|
|
673
|
+
if (name !== void 0) info.qualifierName = name;
|
|
674
|
+
}
|
|
617
675
|
}
|
|
618
676
|
if (beat.kind === "line" || beat.kind === "text") {
|
|
677
|
+
const pad = this.host.padIndex.get(beat.id);
|
|
678
|
+
info.padAfter = pad?.resolved ?? import_model2.DEFAULT_PAD_AFTER;
|
|
679
|
+
if (pad?.own !== void 0) info.ownPadAfter = pad.own;
|
|
619
680
|
const source = this.host.defaultStrings[beat.id];
|
|
620
681
|
if (source !== void 0) info.text = source;
|
|
621
682
|
}
|
|
@@ -841,7 +902,7 @@ var Engine = class _Engine {
|
|
|
841
902
|
const flows = {};
|
|
842
903
|
for (const [id, flow] of this.flowsById) flows[id] = flow.snapshot();
|
|
843
904
|
return {
|
|
844
|
-
version:
|
|
905
|
+
version: import_model3.SAVE_VERSION,
|
|
845
906
|
...this.host.ownsRegistry ? { registry: this.host.registry.save() } : {},
|
|
846
907
|
sharedVisits: Object.fromEntries(this.host.sharedVisits),
|
|
847
908
|
sharedSelectors: serialiseSelectors(this.host.sharedSelectors),
|
|
@@ -858,7 +919,7 @@ var Engine = class _Engine {
|
|
|
858
919
|
loadGame(save) {
|
|
859
920
|
this.refuseInCheckpoint("loadGame");
|
|
860
921
|
const version = save.version;
|
|
861
|
-
if (version !== 2 && version !==
|
|
922
|
+
if (version !== 2 && version !== import_model3.SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);
|
|
862
923
|
const flows = save.flows;
|
|
863
924
|
if (typeof flows !== "object" || flows === null || Array.isArray(flows)) throw new Error("malformed save: no flows");
|
|
864
925
|
this.assertExternalScopes();
|
|
@@ -1409,7 +1470,8 @@ var Flow = class {
|
|
|
1409
1470
|
const node = this.host.nodeIndex.get(o.id);
|
|
1410
1471
|
if (!node) continue;
|
|
1411
1472
|
byId.set(o.id, node);
|
|
1412
|
-
|
|
1473
|
+
const { prompt: saved, ...rest } = o;
|
|
1474
|
+
options.push({ ...rest, ...saved ? { prompt: { ...saved, padAfter: saved.padAfter ?? this.promptPadOf(node) } } : {} });
|
|
1413
1475
|
}
|
|
1414
1476
|
if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };
|
|
1415
1477
|
}
|
|
@@ -1418,7 +1480,7 @@ var Flow = class {
|
|
|
1418
1480
|
if (this.pendingPromptOwnerId) {
|
|
1419
1481
|
const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);
|
|
1420
1482
|
const beat = owner ? this.authoredPromptOf(owner) : void 0;
|
|
1421
|
-
if (beat) this.pendingPrompt = { beat, ...c.pendingPrompt ? { shown: { ...c.pendingPrompt } } : {} };
|
|
1483
|
+
if (beat) this.pendingPrompt = { beat, ...c.pendingPrompt ? { shown: { ...c.pendingPrompt, padAfter: c.pendingPrompt.padAfter ?? this.padOf(beat.id) } } : {} };
|
|
1422
1484
|
else this.pendingPromptOwnerId = null;
|
|
1423
1485
|
}
|
|
1424
1486
|
}
|
|
@@ -1788,7 +1850,7 @@ var Flow = class {
|
|
|
1788
1850
|
case "gameEvent":
|
|
1789
1851
|
return { type: "gameEvent", id: beat.id, gameData: beat.gameData, ...withTags };
|
|
1790
1852
|
case "text":
|
|
1791
|
-
return { type: "text", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), gameData: beat.gameData, ...withTags };
|
|
1853
|
+
return { type: "text", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), padAfter: this.padOf(beat.id), gameData: beat.gameData, ...withTags };
|
|
1792
1854
|
case "line": {
|
|
1793
1855
|
const raw = this.resolveString(beat.id);
|
|
1794
1856
|
const off = !this.host.captionsOn;
|
|
@@ -1802,6 +1864,10 @@ var Flow = class {
|
|
|
1802
1864
|
character: silent ? void 0 : beat.character,
|
|
1803
1865
|
characterName: silent ? void 0 : this.resolveCharacterName(beat.character),
|
|
1804
1866
|
direction: silent ? void 0 : beat.direction,
|
|
1867
|
+
qualifier: silent ? void 0 : beat.qualifier,
|
|
1868
|
+
qualifierName: silent ? void 0 : this.resolveQualifierName(beat.qualifier),
|
|
1869
|
+
padAfter: this.padOf(beat.id),
|
|
1870
|
+
// a silent line still fires, so it still carries its pause
|
|
1805
1871
|
gameData: beat.gameData,
|
|
1806
1872
|
...withTags
|
|
1807
1873
|
};
|
|
@@ -1840,7 +1906,17 @@ var Flow = class {
|
|
|
1840
1906
|
const beat = this.promptBeatOf(node);
|
|
1841
1907
|
if (!beat) return void 0;
|
|
1842
1908
|
const text = this.interpolate(this.resolveString(beat.id));
|
|
1843
|
-
|
|
1909
|
+
const padAfter = this.padOf(beat.id);
|
|
1910
|
+
return beat.kind === "line" ? {
|
|
1911
|
+
kind: "line",
|
|
1912
|
+
text: this.captionLine(text),
|
|
1913
|
+
character: beat.character,
|
|
1914
|
+
characterName: this.resolveCharacterName(beat.character),
|
|
1915
|
+
direction: beat.direction,
|
|
1916
|
+
qualifier: beat.qualifier,
|
|
1917
|
+
qualifierName: this.resolveQualifierName(beat.qualifier),
|
|
1918
|
+
padAfter
|
|
1919
|
+
} : { kind: "text", text, padAfter };
|
|
1844
1920
|
}
|
|
1845
1921
|
/** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
|
|
1846
1922
|
authoredPromptOf(node) {
|
|
@@ -1851,7 +1927,7 @@ var Flow = class {
|
|
|
1851
1927
|
promptResult(beat, shown) {
|
|
1852
1928
|
const tags = tagsOf(this.host, beat.id);
|
|
1853
1929
|
const withTags = tags && tags.length ? { tags } : {};
|
|
1854
|
-
if (shown.kind === "text") return { type: "text", id: beat.id, text: shown.text, gameData: beat.gameData, ...withTags };
|
|
1930
|
+
if (shown.kind === "text") return { type: "text", id: beat.id, text: shown.text, padAfter: this.padOf(beat.id), gameData: beat.gameData, ...withTags };
|
|
1855
1931
|
return {
|
|
1856
1932
|
type: "line",
|
|
1857
1933
|
id: beat.id,
|
|
@@ -1859,6 +1935,9 @@ var Flow = class {
|
|
|
1859
1935
|
character: shown.character,
|
|
1860
1936
|
characterName: shown.characterName,
|
|
1861
1937
|
direction: shown.direction,
|
|
1938
|
+
qualifier: shown.qualifier,
|
|
1939
|
+
qualifierName: shown.qualifierName,
|
|
1940
|
+
padAfter: this.padOf(beat.id),
|
|
1862
1941
|
gameData: beat.gameData,
|
|
1863
1942
|
...withTags
|
|
1864
1943
|
};
|
|
@@ -1872,7 +1951,7 @@ var Flow = class {
|
|
|
1872
1951
|
/** The first snippet with a line/text beat within a child list, depth-first in authored order. */
|
|
1873
1952
|
firstTextSnippetIn(children) {
|
|
1874
1953
|
let found;
|
|
1875
|
-
(0,
|
|
1954
|
+
(0, import_model2.walkNodes)(children, (n) => {
|
|
1876
1955
|
if (!found && n.type === "snippet" && (n.beats ?? []).some((b) => b.kind === "line" || b.kind === "text")) {
|
|
1877
1956
|
found = n;
|
|
1878
1957
|
}
|
|
@@ -1892,9 +1971,29 @@ var Flow = class {
|
|
|
1892
1971
|
resolveCharacterName(character) {
|
|
1893
1972
|
if (character === void 0) return void 0;
|
|
1894
1973
|
if (this.host.emitIds) return void 0;
|
|
1895
|
-
const key = (0,
|
|
1974
|
+
const key = (0, import_model2.castStringKey)(character);
|
|
1896
1975
|
return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.castDisplay.get(character);
|
|
1897
1976
|
}
|
|
1977
|
+
/** The resolved pause of the beat an option shows as its prompt (its authored prompt, else its first content
|
|
1978
|
+
* line), for a saved option that predates prompts carrying one. */
|
|
1979
|
+
promptPadOf(node) {
|
|
1980
|
+
const beat = this.promptBeatOf(node);
|
|
1981
|
+
return beat ? this.padOf(beat.id) : import_model2.DEFAULT_PAD_AFTER;
|
|
1982
|
+
}
|
|
1983
|
+
/** A line or text beat's resolved pause (line padding): its own `padAfter`, else the nearest default above
|
|
1984
|
+
* it, else the project's, else the built-in one; clamped to zero on a snippet's last line. */
|
|
1985
|
+
padOf(beatId) {
|
|
1986
|
+
return this.host.padIndex.get(beatId)?.resolved ?? import_model2.DEFAULT_PAD_AFTER;
|
|
1987
|
+
}
|
|
1988
|
+
/** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
|
|
1989
|
+
* active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
|
|
1990
|
+
* maps the `qualifier` gameId itself). */
|
|
1991
|
+
resolveQualifierName(qualifier) {
|
|
1992
|
+
if (qualifier === void 0) return void 0;
|
|
1993
|
+
if (this.host.emitIds) return void 0;
|
|
1994
|
+
const key = (0, import_model2.qualifierStringKey)(qualifier);
|
|
1995
|
+
return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.qualifierDisplay.get(qualifier);
|
|
1996
|
+
}
|
|
1898
1997
|
/** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
|
|
1899
1998
|
splitRef(ref) {
|
|
1900
1999
|
return splitHostRef(this.host, ref);
|
|
@@ -1975,7 +2074,7 @@ var Flow = class {
|
|
|
1975
2074
|
}
|
|
1976
2075
|
};
|
|
1977
2076
|
function collectCast(nodes, out) {
|
|
1978
|
-
(0,
|
|
2077
|
+
(0, import_model2.walkNodes)(nodes, (n) => {
|
|
1979
2078
|
if (n.type === "group") {
|
|
1980
2079
|
if (n.prompt?.kind === "line" && n.prompt.character) out.add(n.prompt.character);
|
|
1981
2080
|
return;
|
|
@@ -1991,6 +2090,9 @@ function savedPrompt(p) {
|
|
|
1991
2090
|
if (p.character !== void 0) out.character = p.character;
|
|
1992
2091
|
if (p.characterName !== void 0) out.characterName = p.characterName;
|
|
1993
2092
|
if (p.direction !== void 0) out.direction = p.direction;
|
|
2093
|
+
if (p.qualifier !== void 0) out.qualifier = p.qualifier;
|
|
2094
|
+
if (p.qualifierName !== void 0) out.qualifierName = p.qualifierName;
|
|
2095
|
+
if (typeof p.padAfter === "number") out.padAfter = p.padAfter;
|
|
1994
2096
|
return out;
|
|
1995
2097
|
}
|
|
1996
2098
|
function serialiseSelectors(map) {
|
|
@@ -2081,8 +2183,11 @@ function truthy(v) {
|
|
|
2081
2183
|
return v.length > 0;
|
|
2082
2184
|
}
|
|
2083
2185
|
|
|
2186
|
+
// src/index.ts
|
|
2187
|
+
var import_model5 = require("@patterkit/model");
|
|
2188
|
+
|
|
2084
2189
|
// src/describe.ts
|
|
2085
|
-
var
|
|
2190
|
+
var import_model4 = require("@patterkit/model");
|
|
2086
2191
|
var isShared = (d, scopeDefault) => d.shared ?? scopeDefault;
|
|
2087
2192
|
function summariseProperty(d, scopeDefault) {
|
|
2088
2193
|
return {
|
|
@@ -2135,11 +2240,11 @@ function describeBundle(bundle) {
|
|
|
2135
2240
|
const sceneProps = [];
|
|
2136
2241
|
for (const scene of Object.values(bundle.scenes)) {
|
|
2137
2242
|
counts.scenes++;
|
|
2138
|
-
const gameId = (0,
|
|
2243
|
+
const gameId = (0, import_model4.effectiveGameId)(scene);
|
|
2139
2244
|
addresses.push({
|
|
2140
2245
|
gameId,
|
|
2141
2246
|
name: scene.name,
|
|
2142
|
-
blocks: scene.blocks.map((b) => ({ gameId: (0,
|
|
2247
|
+
blocks: scene.blocks.map((b) => ({ gameId: (0, import_model4.effectiveGameId)(b), name: b.name }))
|
|
2143
2248
|
});
|
|
2144
2249
|
for (const block of scene.blocks) countBlock(block, counts);
|
|
2145
2250
|
if (scene.sceneProps?.length) {
|
|
@@ -2204,6 +2309,7 @@ function effectiveGameData(fields, node) {
|
|
|
2204
2309
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2205
2310
|
0 && (module.exports = {
|
|
2206
2311
|
Checkpoint,
|
|
2312
|
+
DEFAULT_PAD_AFTER,
|
|
2207
2313
|
Engine,
|
|
2208
2314
|
Flow,
|
|
2209
2315
|
buildTagIndex,
|