@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 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** and `step.characterName` is omitted - your game localises the IDs in
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 import_model = require("@patterkit/model");
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 import_model2 = require("@patterkit/model");
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, import_model.effectiveGameId)(scene), sceneId);
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, import_model.effectiveGameId)(block), block.id);
164
- (0, import_model.walkNodes)(block.children, (n) => nodeIndex.set(n.id, n));
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 ?? import_model.DEFAULT_CAPTION_DELIMITERS).open,
240
- captionClose: (bundle.closedCaptions ?? import_model.DEFAULT_CAPTION_DELIMITERS).close,
241
- captionCharacter: bundle.closedCaptions?.character || import_model.DEFAULT_CAPTION_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, import_model.effectiveGameId)(scene) : void 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, import_model.effectiveGameId)(block) : void 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, import_model.effectiveGameId)(scene) ? { gameId: (0, import_model.effectiveGameId)(scene) } : {},
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, import_model.effectiveGameId)(block) ? { gameId: (0, import_model.effectiveGameId)(block) } : {},
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, import_model.walkNodes)(block.children, (n) => {
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, import_model.castStringKey)(beat.character)] ?? this.host.castDisplay.get(beat.character);
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: import_model2.SAVE_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 !== import_model2.SAVE_VERSION) throw new Error(`unsupported save version: ${String(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
- options.push({ ...o });
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
- return beat.kind === "line" ? { kind: "line", text: this.captionLine(text), character: beat.character, characterName: this.resolveCharacterName(beat.character), direction: beat.direction } : { kind: "text", text };
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, import_model.walkNodes)(children, (n) => {
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, import_model.castStringKey)(character);
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, import_model.walkNodes)(nodes, (n) => {
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 import_model3 = require("@patterkit/model");
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, import_model3.effectiveGameId)(scene);
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, import_model3.effectiveGameId)(b), name: b.name }))
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,