@patterkit/runtime 0.24.0 → 0.26.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 +23 -0
- package/LICENSE +21 -0
- package/dist/index.cjs +57 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +58 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,29 @@ version number always means the same runtime behaviour. This package is versione
|
|
|
33
33
|
|
|
34
34
|
## [Unreleased]
|
|
35
35
|
|
|
36
|
+
## [0.26.0] - 2026-10-14
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`flow.stepFor(beatId)`**: the step this flow would deliver for a beat right now, built by the same function
|
|
41
|
+
`advance` uses, so it carries the text in the active locale (interpolated, unless the project is voiced, with
|
|
42
|
+
closed captions applied) and the speaker's names and label, its pause, Game Data, and tags. It doesn't move the
|
|
43
|
+
flow, count a visit, or run an effect. Undefined for an id that is neither a beat nor an option's prompt. The
|
|
44
|
+
conformance corpus pins it on every runtime.
|
|
45
|
+
|
|
46
|
+
## [0.25.0] - 2026-10-14
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Speaker labels**: a line step, a line choice prompt, and a beat's info carry `speakerLabel`, the name to
|
|
51
|
+
show for the speaker, localised. With a qualifier it is the character's own label for that pair when the
|
|
52
|
+
project gives one (`speaker:<NAME>:<qualifier>`), else the project's speaker pattern (`speaker:pattern`,
|
|
53
|
+
`{name} ({qualifier})` by default) filled with the shown name and the qualifier's shown name; with none, the
|
|
54
|
+
shown name (the token when the cast gives none). Each piece follows the active locale, then the default, then
|
|
55
|
+
the authored value. A translated pair label counts only for a pair the project overrides. Absent with no
|
|
56
|
+
speaker, on a silenced line, and in IDs-only mode. Saved choice prompts carry it, and an older save works it
|
|
57
|
+
out again on load.
|
|
58
|
+
|
|
36
59
|
## [0.24.0] - 2026-10-11
|
|
37
60
|
|
|
38
61
|
### Added
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ian Thomas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
CHANGED
|
@@ -134,6 +134,25 @@ function padIndexFor(bundle) {
|
|
|
134
134
|
}
|
|
135
135
|
return index;
|
|
136
136
|
}
|
|
137
|
+
var beatIndexCache = /* @__PURE__ */ new WeakMap();
|
|
138
|
+
function beatIndexFor(bundle) {
|
|
139
|
+
let index = beatIndexCache.get(bundle);
|
|
140
|
+
if (index) return index;
|
|
141
|
+
index = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
143
|
+
for (const block of scene.blocks) {
|
|
144
|
+
(0, import_model2.walkNodes)(block.children, (n) => {
|
|
145
|
+
if (n.type === "group") {
|
|
146
|
+
if (n.prompt) index.set(n.prompt.id, n.prompt);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
for (const beat of n.beats ?? []) index.set(beat.id, beat);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
beatIndexCache.set(bundle, index);
|
|
154
|
+
return index;
|
|
155
|
+
}
|
|
137
156
|
function tagsOf(host, id) {
|
|
138
157
|
return host.tagIndex.get(id)?.slice();
|
|
139
158
|
}
|
|
@@ -202,6 +221,9 @@ var Engine = class _Engine {
|
|
|
202
221
|
for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);
|
|
203
222
|
const qualifierDisplay = /* @__PURE__ */ new Map();
|
|
204
223
|
for (const q of bundle.qualifiers ?? []) if (q?.gameId) qualifierDisplay.set(q.gameId, q.name);
|
|
224
|
+
const speakerLabels = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const c of bundle.cast ?? []) for (const [q, label] of Object.entries(c.qualifierLabels ?? {})) if (typeof label === "string") speakerLabels.set(`${c.name}\0${q}`, label);
|
|
226
|
+
const speakerPattern = bundle.speakerPattern ?? import_model2.DEFAULT_SPEAKER_PATTERN;
|
|
205
227
|
this.defaultSeed = (options.seed ?? 2654435769) >>> 0;
|
|
206
228
|
const nodeIndex = /* @__PURE__ */ new Map();
|
|
207
229
|
const blockIndex = /* @__PURE__ */ new Map();
|
|
@@ -263,6 +285,8 @@ var Engine = class _Engine {
|
|
|
263
285
|
defaultStrings,
|
|
264
286
|
castDisplay,
|
|
265
287
|
qualifierDisplay,
|
|
288
|
+
speakerLabels,
|
|
289
|
+
speakerPattern,
|
|
266
290
|
nodeIndex,
|
|
267
291
|
blockIndex,
|
|
268
292
|
blockById,
|
|
@@ -673,6 +697,8 @@ var Engine = class _Engine {
|
|
|
673
697
|
const name = this.host.defaultStrings[(0, import_model2.qualifierStringKey)(beat.qualifier)] ?? this.host.qualifierDisplay.get(beat.qualifier);
|
|
674
698
|
if (name !== void 0) info.qualifierName = name;
|
|
675
699
|
}
|
|
700
|
+
const label = speakerLabelOf(this.host, beat.character, beat.qualifier, true);
|
|
701
|
+
if (label !== void 0) info.speakerLabel = label;
|
|
676
702
|
}
|
|
677
703
|
if (beat.kind === "line" || beat.kind === "text") {
|
|
678
704
|
const pad = this.host.padIndex.get(beat.id);
|
|
@@ -1379,6 +1405,18 @@ var Flow = class {
|
|
|
1379
1405
|
getVisitCounts() {
|
|
1380
1406
|
return Object.fromEntries(this.visitCounts);
|
|
1381
1407
|
}
|
|
1408
|
+
/**
|
|
1409
|
+
* The step this flow would deliver for beat `beatId` right now, as `advance` would give it: its text in the
|
|
1410
|
+
* active locale, interpolated against this flow's values (unless the project is voiced), closed captions
|
|
1411
|
+
* applied, and the speaker's names and label, its pause, Game Data, and tags. Built by the same function that
|
|
1412
|
+
* builds a delivered step, so the two can't differ. It doesn't move the flow, count a visit, or run an effect.
|
|
1413
|
+
* Undefined for an id that is neither a beat nor an option's prompt. For a game that keeps lines by id and
|
|
1414
|
+
* shows them again (after a load, or a language switch).
|
|
1415
|
+
*/
|
|
1416
|
+
stepFor(beatId) {
|
|
1417
|
+
const beat = beatIndexFor(this.host.bundle).get(beatId);
|
|
1418
|
+
return beat ? this.beatResult(beat) : void 0;
|
|
1419
|
+
}
|
|
1382
1420
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
1383
1421
|
getChoices() {
|
|
1384
1422
|
return this.pendingChoice?.options ?? [];
|
|
@@ -1511,7 +1549,8 @@ var Flow = class {
|
|
|
1511
1549
|
if (!node) continue;
|
|
1512
1550
|
byId.set(o.id, node);
|
|
1513
1551
|
const { prompt: saved, ...rest } = o;
|
|
1514
|
-
|
|
1552
|
+
const label = saved?.kind === "line" && saved.speakerLabel === void 0 ? speakerLabelOf(this.host, saved.character, saved.qualifier) : void 0;
|
|
1553
|
+
options.push({ ...rest, ...saved ? { prompt: { ...saved, padAfter: saved.padAfter ?? this.promptPadOf(node), ...label !== void 0 ? { speakerLabel: label } : {} } } : {} });
|
|
1515
1554
|
}
|
|
1516
1555
|
if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };
|
|
1517
1556
|
}
|
|
@@ -1520,7 +1559,9 @@ var Flow = class {
|
|
|
1520
1559
|
if (this.pendingPromptOwnerId) {
|
|
1521
1560
|
const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);
|
|
1522
1561
|
const beat = owner ? this.authoredPromptOf(owner) : void 0;
|
|
1523
|
-
|
|
1562
|
+
const saved = c.pendingPrompt;
|
|
1563
|
+
const label = saved?.kind === "line" && saved.speakerLabel === void 0 ? speakerLabelOf(this.host, saved.character, saved.qualifier) : void 0;
|
|
1564
|
+
if (beat) this.pendingPrompt = { beat, ...saved ? { shown: { ...saved, padAfter: saved.padAfter ?? this.padOf(beat.id), ...label !== void 0 ? { speakerLabel: label } : {} } } : {} };
|
|
1524
1565
|
else this.pendingPromptOwnerId = null;
|
|
1525
1566
|
}
|
|
1526
1567
|
}
|
|
@@ -1906,6 +1947,7 @@ var Flow = class {
|
|
|
1906
1947
|
direction: silent ? void 0 : beat.direction,
|
|
1907
1948
|
qualifier: silent ? void 0 : beat.qualifier,
|
|
1908
1949
|
qualifierName: silent ? void 0 : this.resolveQualifierName(beat.qualifier),
|
|
1950
|
+
speakerLabel: silent ? void 0 : speakerLabelOf(this.host, beat.character, beat.qualifier),
|
|
1909
1951
|
padAfter: this.padOf(beat.id),
|
|
1910
1952
|
// a silent line still fires, so it still carries its pause
|
|
1911
1953
|
gameData: beat.gameData,
|
|
@@ -1955,6 +1997,7 @@ var Flow = class {
|
|
|
1955
1997
|
direction: beat.direction,
|
|
1956
1998
|
qualifier: beat.qualifier,
|
|
1957
1999
|
qualifierName: this.resolveQualifierName(beat.qualifier),
|
|
2000
|
+
speakerLabel: speakerLabelOf(this.host, beat.character, beat.qualifier),
|
|
1958
2001
|
padAfter
|
|
1959
2002
|
} : { kind: "text", text, padAfter };
|
|
1960
2003
|
}
|
|
@@ -1977,6 +2020,7 @@ var Flow = class {
|
|
|
1977
2020
|
direction: shown.direction,
|
|
1978
2021
|
qualifier: shown.qualifier,
|
|
1979
2022
|
qualifierName: shown.qualifierName,
|
|
2023
|
+
speakerLabel: shown.speakerLabel,
|
|
1980
2024
|
padAfter: this.padOf(beat.id),
|
|
1981
2025
|
gameData: beat.gameData,
|
|
1982
2026
|
...withTags
|
|
@@ -2122,6 +2166,16 @@ function collectCast(nodes, out) {
|
|
|
2122
2166
|
for (const beat of n.beats ?? []) if (beat.kind === "line" && beat.character) out.add(beat.character);
|
|
2123
2167
|
});
|
|
2124
2168
|
}
|
|
2169
|
+
function speakerLabelOf(host, character, qualifier, source = false) {
|
|
2170
|
+
if (!character || host.emitIds) return void 0;
|
|
2171
|
+
const lookup = (key) => (source ? void 0 : host.strings[key]) ?? host.defaultStrings[key];
|
|
2172
|
+
const name = lookup((0, import_model2.castStringKey)(character)) ?? host.castDisplay.get(character) ?? character;
|
|
2173
|
+
if (qualifier === void 0) return name;
|
|
2174
|
+
const own = host.speakerLabels.get(`${character}\0${qualifier}`);
|
|
2175
|
+
if (own !== void 0) return lookup((0, import_model2.speakerLabelKey)(character, qualifier)) ?? own;
|
|
2176
|
+
const shown = lookup((0, import_model2.qualifierStringKey)(qualifier)) ?? host.qualifierDisplay.get(qualifier) ?? qualifier;
|
|
2177
|
+
return (0, import_model2.formatSpeakerLabel)(lookup(import_model2.SPEAKER_PATTERN_KEY) ?? host.speakerPattern, name, shown);
|
|
2178
|
+
}
|
|
2125
2179
|
function gameDataField(gameData) {
|
|
2126
2180
|
return gameData && Object.keys(gameData).length ? { gameData } : {};
|
|
2127
2181
|
}
|
|
@@ -2132,6 +2186,7 @@ function savedPrompt(p) {
|
|
|
2132
2186
|
if (p.direction !== void 0) out.direction = p.direction;
|
|
2133
2187
|
if (p.qualifier !== void 0) out.qualifier = p.qualifier;
|
|
2134
2188
|
if (p.qualifierName !== void 0) out.qualifierName = p.qualifierName;
|
|
2189
|
+
if (p.speakerLabel !== void 0) out.speakerLabel = p.speakerLabel;
|
|
2135
2190
|
if (typeof p.padAfter === "number") out.padAfter = p.padAfter;
|
|
2136
2191
|
return out;
|
|
2137
2192
|
}
|