@patterkit/runtime 0.20.0 → 0.22.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/dist/index.d.cts CHANGED
@@ -14,6 +14,8 @@ type StepResult = {
14
14
  character?: string;
15
15
  characterName?: string;
16
16
  direction?: string;
17
+ qualifier?: string;
18
+ qualifierName?: string;
17
19
  gameData?: GameData;
18
20
  tags?: string[];
19
21
  } | {
@@ -44,6 +46,10 @@ interface BeatInfo {
44
46
  characterName?: string;
45
47
  /** Performance direction (line only). */
46
48
  direction?: string;
49
+ /** Speaker qualifier `gameId` (line only), e.g. `vo`. */
50
+ qualifier?: string;
51
+ /** The qualifier's shown name (source locale), e.g. `V.O.`. */
52
+ qualifierName?: string;
47
53
  /** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */
48
54
  text?: string;
49
55
  /** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */
@@ -111,6 +117,9 @@ interface ChoicePrompt {
111
117
  /** The speaker's resolved player-facing name (locale-aware; absent when the character has none). */
112
118
  characterName?: string;
113
119
  direction?: string;
120
+ /** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
121
+ qualifier?: string;
122
+ qualifierName?: string;
114
123
  }
115
124
  /** A single option of a pending `choice` group. */
116
125
  interface ChoiceOption {
@@ -352,6 +361,8 @@ interface FlowHost {
352
361
  defaultStrings: Record<string, string>;
353
362
  /** Cast canonical name -> authoring `displayName` (the unlocalised fallback when no loc string exists). */
354
363
  castDisplay: Map<string, string>;
364
+ /** Speaker qualifier gameId -> authored name (the unlocalised fallback when no loc string exists). */
365
+ qualifierDisplay: Map<string, string>;
355
366
  nodeIndex: Map<string, SelectableNode>;
356
367
  blockIndex: Map<string, {
357
368
  sceneId: string;
@@ -680,8 +691,6 @@ declare class Engine {
680
691
  * across the family 2026-09-05, after a Storylets venue clock and this project's coverage driver
681
692
  * were each refused the one property they existed to move.) */
682
693
  setProperty(ref: string, value: ScalarValue): void;
683
- /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
684
- * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
685
694
  /** The run's decisions, in order, each naming the flow it happened in. Empty unless the
686
695
  * run was opened with `log: true`. A flow's own log stays flow-local; this is the only
687
696
  * place a story spanning several flows reads as one sequence. */
@@ -696,6 +705,8 @@ declare class Engine {
696
705
  /** How many times each node has been entered across every flow, by node id: the shared count. With a
697
706
  * flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
698
707
  getVisitCounts(): Record<string, number>;
708
+ /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
709
+ * value, declared default (for reset), and enum options. Every runtime has the same call. */
699
710
  listProperties(): PropertyRow[];
700
711
  private splitShared;
701
712
  /** The shared `@patter` values alone, as `{ patter: {...} }`.
@@ -994,6 +1005,10 @@ declare class Flow {
994
1005
  * locale, else the authoring `displayName`. Undefined when the character has no display name at all
995
1006
  * (the host falls back to the `character` token itself). */
996
1007
  private resolveCharacterName;
1008
+ /** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
1009
+ * active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
1010
+ * maps the `qualifier` gameId itself). */
1011
+ private resolveQualifierName;
997
1012
  /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
998
1013
  private splitRef;
999
1014
  /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ type StepResult = {
14
14
  character?: string;
15
15
  characterName?: string;
16
16
  direction?: string;
17
+ qualifier?: string;
18
+ qualifierName?: string;
17
19
  gameData?: GameData;
18
20
  tags?: string[];
19
21
  } | {
@@ -44,6 +46,10 @@ interface BeatInfo {
44
46
  characterName?: string;
45
47
  /** Performance direction (line only). */
46
48
  direction?: string;
49
+ /** Speaker qualifier `gameId` (line only), e.g. `vo`. */
50
+ qualifier?: string;
51
+ /** The qualifier's shown name (source locale), e.g. `V.O.`. */
52
+ qualifierName?: string;
47
53
  /** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */
48
54
  text?: string;
49
55
  /** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */
@@ -111,6 +117,9 @@ interface ChoicePrompt {
111
117
  /** The speaker's resolved player-facing name (locale-aware; absent when the character has none). */
112
118
  characterName?: string;
113
119
  direction?: string;
120
+ /** The speaker qualifier's `gameId` (`vo`), and its resolved shown name (locale-aware). */
121
+ qualifier?: string;
122
+ qualifierName?: string;
114
123
  }
115
124
  /** A single option of a pending `choice` group. */
116
125
  interface ChoiceOption {
@@ -352,6 +361,8 @@ interface FlowHost {
352
361
  defaultStrings: Record<string, string>;
353
362
  /** Cast canonical name -> authoring `displayName` (the unlocalised fallback when no loc string exists). */
354
363
  castDisplay: Map<string, string>;
364
+ /** Speaker qualifier gameId -> authored name (the unlocalised fallback when no loc string exists). */
365
+ qualifierDisplay: Map<string, string>;
355
366
  nodeIndex: Map<string, SelectableNode>;
356
367
  blockIndex: Map<string, {
357
368
  sceneId: string;
@@ -680,8 +691,6 @@ declare class Engine {
680
691
  * across the family 2026-09-05, after a Storylets venue clock and this project's coverage driver
681
692
  * were each refused the one property they existed to move.) */
682
693
  setProperty(ref: string, value: ScalarValue): void;
683
- /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
684
- * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
685
694
  /** The run's decisions, in order, each naming the flow it happened in. Empty unless the
686
695
  * run was opened with `log: true`. A flow's own log stays flow-local; this is the only
687
696
  * place a story spanning several flows reads as one sequence. */
@@ -696,6 +705,8 @@ declare class Engine {
696
705
  /** How many times each node has been entered across every flow, by node id: the shared count. With a
697
706
  * flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
698
707
  getVisitCounts(): Record<string, number>;
708
+ /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
709
+ * value, declared default (for reset), and enum options. Every runtime has the same call. */
699
710
  listProperties(): PropertyRow[];
700
711
  private splitShared;
701
712
  /** The shared `@patter` values alone, as `{ patter: {...} }`.
@@ -994,6 +1005,10 @@ declare class Flow {
994
1005
  * locale, else the authoring `displayName`. Undefined when the character has no display name at all
995
1006
  * (the host falls back to the `character` token itself). */
996
1007
  private resolveCharacterName;
1008
+ /** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
1009
+ * active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
1010
+ * maps the `qualifier` gameId itself). */
1011
+ private resolveQualifierName;
997
1012
  /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
998
1013
  private splitRef;
999
1014
  /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { matchedSpecificity as scoreSpecificity } from "@wildwinter/expr-specifi
4
4
  import { ScopeRegistry } from "@wildwinter/scoperegistry";
5
5
  import { defaultFor, PropertyBag } from "@wildwinter/scoperegistry";
6
6
  import { patterDialect, interpolate, splitRef, stripCaptions } from "@patterkit/dialect";
7
- import { walkNodes, effectiveGameId, castStringKey, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from "@patterkit/model";
7
+ import { walkNodes, effectiveGameId, castStringKey, qualifierStringKey, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from "@patterkit/model";
8
8
 
9
9
  // src/tags.ts
10
10
  function dedupe(tags) {
@@ -43,6 +43,18 @@ function buildTagIndex(bundle) {
43
43
  // src/engine.ts
44
44
  import { SAVE_VERSION } from "@patterkit/model";
45
45
  var astCache = /* @__PURE__ */ new WeakMap();
46
+ var tagIndexCache = /* @__PURE__ */ new WeakMap();
47
+ function tagIndexFor(bundle) {
48
+ let index = tagIndexCache.get(bundle);
49
+ if (!index) {
50
+ index = buildTagIndex(bundle);
51
+ tagIndexCache.set(bundle, index);
52
+ }
53
+ return index;
54
+ }
55
+ function tagsOf(host, id) {
56
+ return host.tagIndex.get(id)?.slice();
57
+ }
46
58
  var OWNER = "Patter";
47
59
  var esc = (id) => id.replace(/%/g, "%25").replace(/\//g, "%2F");
48
60
  var OWNS_REGISTRY = /* @__PURE__ */ Symbol("ownsRegistry");
@@ -105,6 +117,8 @@ var Engine = class _Engine {
105
117
  }
106
118
  const castDisplay = /* @__PURE__ */ new Map();
107
119
  for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);
120
+ const qualifierDisplay = /* @__PURE__ */ new Map();
121
+ for (const q of bundle.qualifiers ?? []) if (q?.gameId) qualifierDisplay.set(q.gameId, q.name);
108
122
  this.defaultSeed = (options.seed ?? 2654435769) >>> 0;
109
123
  const nodeIndex = /* @__PURE__ */ new Map();
110
124
  const blockIndex = /* @__PURE__ */ new Map();
@@ -165,13 +179,14 @@ var Engine = class _Engine {
165
179
  strings,
166
180
  defaultStrings,
167
181
  castDisplay,
182
+ qualifierDisplay,
168
183
  nodeIndex,
169
184
  blockIndex,
170
185
  blockById,
171
186
  sceneGameIdToId: this.sceneGameIdToId,
172
187
  blockGameIdToId: this.blockGameIdToId,
173
188
  // same instances the engine resolves with
174
- tagIndex: buildTagIndex(bundle),
189
+ tagIndex: tagIndexFor(bundle),
175
190
  registry,
176
191
  ownsRegistry,
177
192
  patterBag,
@@ -430,18 +445,18 @@ var Engine = class _Engine {
430
445
  * delivered step carries. Empty array for an unknown id or a beat with no tags anywhere up the chain.
431
446
  */
432
447
  tagsForBeat(beatId) {
433
- return this.host.tagIndex.get(beatId) ?? [];
448
+ return tagsOf(this.host, beatId) ?? [];
434
449
  }
435
450
  /** A scene's own tags (by internal id or gameId address). Empty when none / unknown. */
436
451
  tagsForScene(sceneRef) {
437
452
  const id = this.resolveSceneRef(sceneRef);
438
- return (id != null ? this.host.tagIndex.get(id) : void 0) ?? [];
453
+ return (id != null ? tagsOf(this.host, id) : void 0) ?? [];
439
454
  }
440
455
  /** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */
441
456
  tagsForBlock(sceneRef, blockRef) {
442
457
  const sceneId = this.resolveSceneRef(sceneRef);
443
458
  const id = this.resolveBlockRef(sceneId, blockRef);
444
- return (id != null ? this.host.tagIndex.get(id) : void 0) ?? [];
459
+ return (id != null ? tagsOf(this.host, id) : void 0) ?? [];
445
460
  }
446
461
  /**
447
462
  * A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly
@@ -560,7 +575,7 @@ var Engine = class _Engine {
560
575
  }
561
576
  /** One beat's static data (source locale), the same shape a delivered step carries. */
562
577
  beatInfo(beat) {
563
- const tags = this.host.tagIndex.get(beat.id);
578
+ const tags = tagsOf(this.host, beat.id);
564
579
  const info = { id: beat.id, kind: beat.kind };
565
580
  if (beat.kind === "line") {
566
581
  if (beat.character !== void 0) {
@@ -569,6 +584,11 @@ var Engine = class _Engine {
569
584
  if (name !== void 0) info.characterName = name;
570
585
  }
571
586
  if (beat.direction !== void 0) info.direction = beat.direction;
587
+ if (beat.qualifier !== void 0) {
588
+ info.qualifier = beat.qualifier;
589
+ const name = this.host.defaultStrings[qualifierStringKey(beat.qualifier)] ?? this.host.qualifierDisplay.get(beat.qualifier);
590
+ if (name !== void 0) info.qualifierName = name;
591
+ }
572
592
  }
573
593
  if (beat.kind === "line" || beat.kind === "text") {
574
594
  const source = this.host.defaultStrings[beat.id];
@@ -580,7 +600,7 @@ var Engine = class _Engine {
580
600
  }
581
601
  /** A `{ tags }` fragment for an id, present only when the id has accumulated tags (keeps output tidy). */
582
602
  tagsField(id) {
583
- const tags = this.host.tagIndex.get(id);
603
+ const tags = tagsOf(this.host, id);
584
604
  return tags && tags.length ? { tags } : {};
585
605
  }
586
606
  /** Retrieve an open flow by id (undefined if none / closed). */
@@ -717,8 +737,6 @@ var Engine = class _Engine {
717
737
  this.host.registry.set(scope, name, value, { host: true });
718
738
  if (this.host.journal && prev !== void 0) this.host.journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));
719
739
  }
720
- /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
721
- * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
722
740
  /** The run's decisions, in order, each naming the flow it happened in. Empty unless the
723
741
  * run was opened with `log: true`. A flow's own log stays flow-local; this is the only
724
742
  * place a story spanning several flows reads as one sequence. */
@@ -750,6 +768,8 @@ var Engine = class _Engine {
750
768
  getVisitCounts() {
751
769
  return Object.fromEntries(this.host.sharedVisits);
752
770
  }
771
+ /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
772
+ * value, declared default (for reset), and enum options. Every runtime has the same call. */
753
773
  listProperties() {
754
774
  return this.host.patterSharedDecls.map((d) => ({
755
775
  name: d.name,
@@ -1737,7 +1757,7 @@ var Flow = class {
1737
1757
  };
1738
1758
  // -- Strings / beats ------------------------------------------------------
1739
1759
  beatResult(beat) {
1740
- const tags = this.host.tagIndex.get(beat.id);
1760
+ const tags = tagsOf(this.host, beat.id);
1741
1761
  const withTags = tags && tags.length ? { tags } : {};
1742
1762
  switch (beat.kind) {
1743
1763
  case "gameEvent":
@@ -1757,6 +1777,8 @@ var Flow = class {
1757
1777
  character: silent ? void 0 : beat.character,
1758
1778
  characterName: silent ? void 0 : this.resolveCharacterName(beat.character),
1759
1779
  direction: silent ? void 0 : beat.direction,
1780
+ qualifier: silent ? void 0 : beat.qualifier,
1781
+ qualifierName: silent ? void 0 : this.resolveQualifierName(beat.qualifier),
1760
1782
  gameData: beat.gameData,
1761
1783
  ...withTags
1762
1784
  };
@@ -1795,7 +1817,15 @@ var Flow = class {
1795
1817
  const beat = this.promptBeatOf(node);
1796
1818
  if (!beat) return void 0;
1797
1819
  const text = this.interpolate(this.resolveString(beat.id));
1798
- return beat.kind === "line" ? { kind: "line", text: this.captionLine(text), character: beat.character, characterName: this.resolveCharacterName(beat.character), direction: beat.direction } : { kind: "text", text };
1820
+ return beat.kind === "line" ? {
1821
+ kind: "line",
1822
+ text: this.captionLine(text),
1823
+ character: beat.character,
1824
+ characterName: this.resolveCharacterName(beat.character),
1825
+ direction: beat.direction,
1826
+ qualifier: beat.qualifier,
1827
+ qualifierName: this.resolveQualifierName(beat.qualifier)
1828
+ } : { kind: "text", text };
1799
1829
  }
1800
1830
  /** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
1801
1831
  authoredPromptOf(node) {
@@ -1804,7 +1834,7 @@ var Flow = class {
1804
1834
  /** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the
1805
1835
  * choice showed. */
1806
1836
  promptResult(beat, shown) {
1807
- const tags = this.host.tagIndex.get(beat.id);
1837
+ const tags = tagsOf(this.host, beat.id);
1808
1838
  const withTags = tags && tags.length ? { tags } : {};
1809
1839
  if (shown.kind === "text") return { type: "text", id: beat.id, text: shown.text, gameData: beat.gameData, ...withTags };
1810
1840
  return {
@@ -1814,6 +1844,8 @@ var Flow = class {
1814
1844
  character: shown.character,
1815
1845
  characterName: shown.characterName,
1816
1846
  direction: shown.direction,
1847
+ qualifier: shown.qualifier,
1848
+ qualifierName: shown.qualifierName,
1817
1849
  gameData: beat.gameData,
1818
1850
  ...withTags
1819
1851
  };
@@ -1850,6 +1882,15 @@ var Flow = class {
1850
1882
  const key = castStringKey(character);
1851
1883
  return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.castDisplay.get(character);
1852
1884
  }
1885
+ /** A speaker qualifier's shown name, resolved as a character's is: the `qualifier:<gameId>` string in the
1886
+ * active locale, else the default locale, else the authored name. Undefined in IDs-only mode (the game
1887
+ * maps the `qualifier` gameId itself). */
1888
+ resolveQualifierName(qualifier) {
1889
+ if (qualifier === void 0) return void 0;
1890
+ if (this.host.emitIds) return void 0;
1891
+ const key = qualifierStringKey(qualifier);
1892
+ return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.qualifierDisplay.get(qualifier);
1893
+ }
1853
1894
  /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
1854
1895
  splitRef(ref) {
1855
1896
  return splitHostRef(this.host, ref);
@@ -1946,6 +1987,8 @@ function savedPrompt(p) {
1946
1987
  if (p.character !== void 0) out.character = p.character;
1947
1988
  if (p.characterName !== void 0) out.characterName = p.characterName;
1948
1989
  if (p.direction !== void 0) out.direction = p.direction;
1990
+ if (p.qualifier !== void 0) out.qualifier = p.qualifier;
1991
+ if (p.qualifierName !== void 0) out.qualifierName = p.qualifierName;
1949
1992
  return out;
1950
1993
  }
1951
1994
  function serialiseSelectors(map) {