@patterkit/runtime 0.1.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 +30 -0
- package/README.md +91 -0
- package/dist/index.cjs +1281 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +626 -0
- package/dist/index.d.ts +626 -0
- package/dist/index.js +1249 -0
- package/dist/index.js.map +1 -0
- package/dist/patterplay.min.js +3 -0
- package/dist/patterplay.min.js.map +1 -0
- package/package.json +36 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { ScalarValue } from '@wildwinter/expr';
|
|
2
|
+
import { ScopeResolver, ScopeRegistry, ScopeDeclaration } from '@wildwinter/scoperegistry';
|
|
3
|
+
import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, GameDataField, GameDataNodeKind } from '@patterkit/model';
|
|
4
|
+
export { Bundle } from '@patterkit/model';
|
|
5
|
+
|
|
6
|
+
type SelectableNode = CompiledGroup | CompiledSnippet;
|
|
7
|
+
/** A property-state snapshot: owned scope -> property name -> value. */
|
|
8
|
+
type EngineSave = Record<string, Record<string, ScalarValue>>;
|
|
9
|
+
/** Serialised `sequence` selector visit state for one group (spec §4 / §7). */
|
|
10
|
+
interface SelectorSnapshot {
|
|
11
|
+
seq?: number;
|
|
12
|
+
bag?: string[];
|
|
13
|
+
last?: string;
|
|
14
|
+
}
|
|
15
|
+
/** One entry on a flow's continuation stack: a position within a container's children. */
|
|
16
|
+
interface StackFrame {
|
|
17
|
+
sceneId: string;
|
|
18
|
+
/** A block id or a run-group id (both are sequential containers). */
|
|
19
|
+
containerId: string;
|
|
20
|
+
index: number;
|
|
21
|
+
/** SNAPSHOT-ONLY (never set on a live frame): the id of the child at `index` when the save was
|
|
22
|
+
* taken. On restore the child is re-found by this id, so a save survives siblings being inserted,
|
|
23
|
+
* removed, or reordered before the cursor (live bundle refresh / patched-game saves). Absent (an
|
|
24
|
+
* older save, or a frame saved at its container's end) falls back to the raw `index`. */
|
|
25
|
+
nextId?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The serialised cursor + scopes + PRNG of a single flow. */
|
|
28
|
+
interface FlowSnapshot {
|
|
29
|
+
/** This flow's owned-scope values = the NOT-shared `@patter` globals (under token "patter"). */
|
|
30
|
+
scopes: EngineSave;
|
|
31
|
+
/** Per-scene NOT-shared `@scene` bags (scene id -> name -> value); persist across re-entries (spec §7). */
|
|
32
|
+
sceneBags: Record<string, Record<string, ScalarValue>>;
|
|
33
|
+
/** This flow's built-in PRNG position (mulberry32 state). */
|
|
34
|
+
rngState: number;
|
|
35
|
+
/** This flow's per-node entry counts (node id -> times entered by this flow). */
|
|
36
|
+
visits: Record<string, number>;
|
|
37
|
+
cursor: {
|
|
38
|
+
flowEnded: boolean;
|
|
39
|
+
currentSceneId: string | null;
|
|
40
|
+
/** The continuation stack (call frames + the active block run). */
|
|
41
|
+
stack: StackFrame[];
|
|
42
|
+
activeSnippetId: string | null;
|
|
43
|
+
beatIndex: number;
|
|
44
|
+
/** The pending choice's exact option set, REPLAYED on load (schema 9.3). */
|
|
45
|
+
pendingChoice: SavedChoice | null;
|
|
46
|
+
/** The chosen option owning a prompt still to be replayed (save taken between choose + advance).
|
|
47
|
+
* Optional / absent in older saves -> no pending prompt. */
|
|
48
|
+
pendingPromptOwnerId?: string | null;
|
|
49
|
+
/** This flow's `sequence` selector cursors. */
|
|
50
|
+
selectors: Record<string, SelectorSnapshot>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A pending choice as saved: the option set the player was shown, restored
|
|
55
|
+
* verbatim - re-deriving on load would re-evaluate conditions (consuming PRNG
|
|
56
|
+
* draws a second time) and could mutate the choice under the player.
|
|
57
|
+
*/
|
|
58
|
+
interface SavedChoice {
|
|
59
|
+
groupId: string;
|
|
60
|
+
options: ChoiceOption[];
|
|
61
|
+
}
|
|
62
|
+
/** A full resumable save-game: shared `@patter` state + every live flow. */
|
|
63
|
+
interface SaveGame {
|
|
64
|
+
version: number;
|
|
65
|
+
/** Shared `@patter` globals (owned scope "patter"). */
|
|
66
|
+
shared: EngineSave;
|
|
67
|
+
/** World-wide per-node entry counts (node id -> times entered by any flow). */
|
|
68
|
+
sharedVisits: Record<string, number>;
|
|
69
|
+
/** Shared selector cursors (node id -> snapshot) for `shared` memoried selectors. */
|
|
70
|
+
sharedSelectors: Record<string, SelectorSnapshot>;
|
|
71
|
+
/** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) - the shared scene props. */
|
|
72
|
+
stageBags: Record<string, Record<string, ScalarValue>>;
|
|
73
|
+
/** Each live flow's snapshot, keyed by flow id. */
|
|
74
|
+
flows: Record<string, FlowSnapshot>;
|
|
75
|
+
}
|
|
76
|
+
/** What `Flow.advance()` surfaces to the host at each stop. */
|
|
77
|
+
type StepResult = {
|
|
78
|
+
type: "line";
|
|
79
|
+
id: string;
|
|
80
|
+
text: string;
|
|
81
|
+
character?: string;
|
|
82
|
+
characterName?: string;
|
|
83
|
+
direction?: string;
|
|
84
|
+
gameData?: GameData;
|
|
85
|
+
tags?: string[];
|
|
86
|
+
} | {
|
|
87
|
+
type: "text";
|
|
88
|
+
id: string;
|
|
89
|
+
text: string;
|
|
90
|
+
gameData?: GameData;
|
|
91
|
+
tags?: string[];
|
|
92
|
+
} | {
|
|
93
|
+
type: "gameEvent";
|
|
94
|
+
id: string;
|
|
95
|
+
gameData?: GameData;
|
|
96
|
+
tags?: string[];
|
|
97
|
+
} | {
|
|
98
|
+
type: "choice";
|
|
99
|
+
groupId: string;
|
|
100
|
+
options: ChoiceOption[];
|
|
101
|
+
} | {
|
|
102
|
+
type: "end";
|
|
103
|
+
};
|
|
104
|
+
/** One beat's static data - the same shape a delivered step carries, resolved at the source locale. */
|
|
105
|
+
interface BeatInfo {
|
|
106
|
+
id: string;
|
|
107
|
+
kind: "line" | "text" | "gameEvent";
|
|
108
|
+
/** Speaker token (line only). */
|
|
109
|
+
character?: string;
|
|
110
|
+
/** Resolved display name for `character` (source locale), if the cast declares one. */
|
|
111
|
+
characterName?: string;
|
|
112
|
+
/** Performance direction (line only). */
|
|
113
|
+
direction?: string;
|
|
114
|
+
/** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */
|
|
115
|
+
text?: string;
|
|
116
|
+
/** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */
|
|
117
|
+
gameData?: GameData;
|
|
118
|
+
/** Accumulated author tags (scene -> block -> group(s) -> snippet -> beat). Omitted when empty. */
|
|
119
|
+
tags?: string[];
|
|
120
|
+
}
|
|
121
|
+
/** A node in the outline tree: a group (with its selector + children) or a snippet (with its beats). */
|
|
122
|
+
interface OutlineNode {
|
|
123
|
+
type: "group" | "snippet";
|
|
124
|
+
id: string;
|
|
125
|
+
tags?: string[];
|
|
126
|
+
selector?: string;
|
|
127
|
+
/** A choice/option group's prompt beat, if any. */
|
|
128
|
+
prompt?: BeatInfo;
|
|
129
|
+
children?: OutlineNode[];
|
|
130
|
+
beats?: BeatInfo[];
|
|
131
|
+
jumpTo?: string;
|
|
132
|
+
jumpMode?: "jump" | "call";
|
|
133
|
+
}
|
|
134
|
+
/** A block in the outline tree. */
|
|
135
|
+
interface OutlineBlock {
|
|
136
|
+
id: string;
|
|
137
|
+
gameId?: string;
|
|
138
|
+
name: string;
|
|
139
|
+
tags?: string[];
|
|
140
|
+
children: OutlineNode[];
|
|
141
|
+
}
|
|
142
|
+
/** A scene in the outline tree. */
|
|
143
|
+
interface OutlineScene {
|
|
144
|
+
id: string;
|
|
145
|
+
gameId?: string;
|
|
146
|
+
name: string;
|
|
147
|
+
tags?: string[];
|
|
148
|
+
blocks: OutlineBlock[];
|
|
149
|
+
}
|
|
150
|
+
/** One beat in document order, with the scene/block/snippet it lives in (the flat view). */
|
|
151
|
+
interface FlatBeat {
|
|
152
|
+
sceneId: string;
|
|
153
|
+
blockId: string;
|
|
154
|
+
snippetId: string;
|
|
155
|
+
beat: BeatInfo;
|
|
156
|
+
}
|
|
157
|
+
/** What {@link Flow.advanceToStop} returns: the beats walked, and the choice / end that stopped it. */
|
|
158
|
+
interface AdvanceToStopResult {
|
|
159
|
+
/** The line / text / game-event beats played on the way to the stop (never a choice / end). */
|
|
160
|
+
played: Array<Extract<StepResult, {
|
|
161
|
+
type: "line" | "text" | "gameEvent";
|
|
162
|
+
}>>;
|
|
163
|
+
stop: Extract<StepResult, {
|
|
164
|
+
type: "choice" | "end";
|
|
165
|
+
}>;
|
|
166
|
+
}
|
|
167
|
+
/** The choice text of an option (spec §5): its `prompt` beat, resolved + interpolated. */
|
|
168
|
+
interface ChoicePrompt {
|
|
169
|
+
kind: "line" | "text";
|
|
170
|
+
/** Display text (interpolated; may be empty - the host can render from gameData / an icon). */
|
|
171
|
+
text: string;
|
|
172
|
+
/** Speaker / direction - present only for a `line` prompt (the PC's spoken choice). */
|
|
173
|
+
character?: string;
|
|
174
|
+
/** The speaker's resolved player-facing name (locale-aware; absent when the character has none). */
|
|
175
|
+
characterName?: string;
|
|
176
|
+
direction?: string;
|
|
177
|
+
}
|
|
178
|
+
/** A single option of a pending `choice` group. */
|
|
179
|
+
interface ChoiceOption {
|
|
180
|
+
/** The option's id (an Option group, or a degenerate option snippet) - pass to `choose()`. */
|
|
181
|
+
id: string;
|
|
182
|
+
/**
|
|
183
|
+
* The option's `prompt` (spec §5) - the choice text as a structured line/text beat. For the
|
|
184
|
+
* degenerate bare-snippet tolerance, derived from the snippet's first content line. Undefined
|
|
185
|
+
* only when even that is absent; internal ids are never leaked as display text.
|
|
186
|
+
*/
|
|
187
|
+
prompt?: ChoicePrompt;
|
|
188
|
+
/** False when the option's condition fails; still returned (greyed) unless hidden. */
|
|
189
|
+
eligible: boolean;
|
|
190
|
+
gameData?: GameData;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The host's **World Properties** resolver: a `{ get, set? }` the game provides so the story can read
|
|
194
|
+
* (and, if you allow it, write) its `@world.*` values at runtime. Property metadata (types, read-only)
|
|
195
|
+
* comes from the compiled bundle's declared world properties; the values themselves live in the host and
|
|
196
|
+
* are never stored or saved by this engine.
|
|
197
|
+
*/
|
|
198
|
+
type WorldResolver = ScopeResolver;
|
|
199
|
+
interface EngineOptions {
|
|
200
|
+
/**
|
|
201
|
+
* Custom float-in-[0,1) source for `random()` / shuffle, shared by all flows.
|
|
202
|
+
* Overrides the built-in seeded PRNG - but its position is NOT captured by
|
|
203
|
+
* `saveGame()`. For resumable runs, use the built-in per-flow seed instead.
|
|
204
|
+
*/
|
|
205
|
+
rng?: () => number;
|
|
206
|
+
/** Default seed for each flow's built-in (serialisable) PRNG; override per flow in `openFlow`. */
|
|
207
|
+
seed?: number;
|
|
208
|
+
/** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.
|
|
209
|
+
* Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
|
|
210
|
+
locale?: string;
|
|
211
|
+
/** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story
|
|
212
|
+
* reads. Omit it and the runtime self-backs `@world` from the declared defaults. Shared by all flows. */
|
|
213
|
+
world?: WorldResolver;
|
|
214
|
+
/**
|
|
215
|
+
* Replay a chosen option's `prompt` as its first played beat (spec §5). Default `false`:
|
|
216
|
+
* the prompt is a label only and `choose()` plays just the option's content. `true`: the
|
|
217
|
+
* prompt beat is delivered first (the choice "spoken back"). A host decision, not authored.
|
|
218
|
+
*/
|
|
219
|
+
replayPromptOnChoose?: boolean;
|
|
220
|
+
/** Closed captions (#214): show non-spoken caption cues inside dialogue lines (the `[sigh]` in
|
|
221
|
+
* `Oh dear. [sigh] What now?`). Default `true` (full text). `false` strips every cue + its delimiters
|
|
222
|
+
* and collapses the whitespace - for a player who hears the audio and doesn't want the captions.
|
|
223
|
+
* Toggle live with `engine.setClosedCaptions(...)`. */
|
|
224
|
+
closedCaptions?: boolean;
|
|
225
|
+
/** Diagnostics hook (opt-in, dev tooling only): fired with the choice's group id whenever a choice runs
|
|
226
|
+
* DRY - no takeable option and no eligible fallback - so it falls through silently. The behaviour is
|
|
227
|
+
* unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
|
|
228
|
+
* that ran dry. Leave it unset in shipped games (zero cost). */
|
|
229
|
+
onDryChoice?: (groupId: string) => void;
|
|
230
|
+
}
|
|
231
|
+
/** One shared `@patter` property, for a live state inspector: its ref, declared type, current value,
|
|
232
|
+
* declared default (for reset), and enum options. Mirrors the Unity / Godot ports' ListProperties. */
|
|
233
|
+
interface PropertyRow {
|
|
234
|
+
ref: string;
|
|
235
|
+
type: PropertyType;
|
|
236
|
+
value: ScalarValue | undefined;
|
|
237
|
+
default: ScalarValue;
|
|
238
|
+
values?: string[];
|
|
239
|
+
}
|
|
240
|
+
/** Options for opening a flow. */
|
|
241
|
+
interface OpenFlowOptions {
|
|
242
|
+
/** Scene to start at - its host-facing gameId (address) OR its internal id; defaults to the
|
|
243
|
+
* bundle's first scene. */
|
|
244
|
+
scene?: string;
|
|
245
|
+
/** Block within the scene to start at - its gameId (scene-scoped address) OR its internal id. */
|
|
246
|
+
block?: string;
|
|
247
|
+
/** Seed for this flow's PRNG (defaults to the engine's `seed`). */
|
|
248
|
+
seed?: number;
|
|
249
|
+
}
|
|
250
|
+
interface SelectorState {
|
|
251
|
+
seq?: number;
|
|
252
|
+
bag?: string[];
|
|
253
|
+
last?: string;
|
|
254
|
+
}
|
|
255
|
+
/** Shared, read-mostly context the engine hands to every flow it owns. */
|
|
256
|
+
interface FlowHost {
|
|
257
|
+
bundle: Bundle;
|
|
258
|
+
/** IDs-only build (`localisation.mode === "ids"`, no source-debug): the engine emits each beat's ID as
|
|
259
|
+
* its text and omits character display names, leaving localisation to the game (use `flow.interpolate`
|
|
260
|
+
* to apply `{@ref}` property replacement to a string the game looked up itself). */
|
|
261
|
+
emitIds: boolean;
|
|
262
|
+
strings: Record<string, string>;
|
|
263
|
+
/** The DEFAULT locale's string table - fallback for a key the active locale is missing (notably the
|
|
264
|
+
* cast display-name keys, seeded there from `displayName`). */
|
|
265
|
+
defaultStrings: Record<string, string>;
|
|
266
|
+
/** Cast canonical name -> authoring `displayName` (the unlocalised fallback when no loc string exists). */
|
|
267
|
+
castDisplay: Map<string, string>;
|
|
268
|
+
nodeIndex: Map<string, SelectableNode>;
|
|
269
|
+
blockIndex: Map<string, {
|
|
270
|
+
sceneId: string;
|
|
271
|
+
}>;
|
|
272
|
+
blockById: Map<string, CompiledBlock>;
|
|
273
|
+
/** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */
|
|
274
|
+
tagIndex: Map<string, string[]>;
|
|
275
|
+
/** The SHARED `@patter` globals (owned scope "patter") + world properties (`@world`). */
|
|
276
|
+
shared: ScopeRegistry;
|
|
277
|
+
/** Decls for the shared `@patter` globals - (re)seed on `engine.reset()`. */
|
|
278
|
+
patterSharedDecls: ScopeDeclaration[];
|
|
279
|
+
/** Decls for the per-flow `@patter` globals - seed each flow's local registry. */
|
|
280
|
+
patterLocalDecls: ScopeDeclaration[];
|
|
281
|
+
/** Lowercase names of the SHARED globals (route a `@patter` ref to engine vs flow). */
|
|
282
|
+
patterSharedNames: Set<string>;
|
|
283
|
+
/** Per-scene set of SHARED `@scene` prop names (route a `@scene` ref to stage vs flow). */
|
|
284
|
+
sceneSharedNames: Map<string, Set<string>>;
|
|
285
|
+
/** World-wide per-node entry counts (node id -> times entered by any flow). */
|
|
286
|
+
sharedVisits: Map<string, number>;
|
|
287
|
+
/** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */
|
|
288
|
+
sharedSelectors: Map<string, SelectorState>;
|
|
289
|
+
/** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */
|
|
290
|
+
stageBags: Map<string, Record<string, ScalarValue>>;
|
|
291
|
+
customRng?: () => number;
|
|
292
|
+
/** Play a chosen option's prompt as its first beat (spec §5); default false. */
|
|
293
|
+
replayPromptOnChoose?: boolean;
|
|
294
|
+
/** Closed captions (#214). `captionsOn`: show caption cues in dialogue lines (default true); when
|
|
295
|
+
* false the engine strips `captionOpen`…`captionClose` spans from line text. Mutable via
|
|
296
|
+
* `setClosedCaptions` (one toggle, all flows - like setLocale). */
|
|
297
|
+
captionsOn: boolean;
|
|
298
|
+
captionOpen: string;
|
|
299
|
+
captionClose: string;
|
|
300
|
+
/** A cast member whose lines are pure captions: when captions are off ALL of its dialogue + speaker is
|
|
301
|
+
* omitted (a silent line), delimiters or not. Default `SFX`. Empty = no caption character. */
|
|
302
|
+
captionCharacter: string;
|
|
303
|
+
/** Diagnostics hook (opt-in, dev only): fired when a choice runs DRY - nothing takeable and no eligible
|
|
304
|
+
* fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
|
|
305
|
+
* harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
|
|
306
|
+
onDryChoice?: (groupId: string) => void;
|
|
307
|
+
/** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on `shared`'s scope
|
|
308
|
+
* set, which is fixed for the engine's life, so every effect target / `{@ref}` slot parses once. */
|
|
309
|
+
refSplitCache: Map<string, {
|
|
310
|
+
scope: string;
|
|
311
|
+
name: string;
|
|
312
|
+
}>;
|
|
313
|
+
}
|
|
314
|
+
declare class Engine {
|
|
315
|
+
private readonly host;
|
|
316
|
+
private readonly defaultSeed;
|
|
317
|
+
private readonly flowsById;
|
|
318
|
+
/** Every locale's string table (the inline `bundle.strings`), kept so the active locale can be swapped
|
|
319
|
+
* live (setLocale) without rebuilding the engine. Reassigned wholesale by `replaceStrings`
|
|
320
|
+
* (live bundle refresh, tier 1), hence not readonly. */
|
|
321
|
+
private allStrings;
|
|
322
|
+
/** The currently active locale (string lookups + character names resolve in it). */
|
|
323
|
+
private currentLocale;
|
|
324
|
+
/** True for a source-only DEBUG build (`localisation: { mode: "ids", sourceDebug: true }`) - the strings
|
|
325
|
+
* are the source language, embedded only so the build can be played; not a shippable localised build. */
|
|
326
|
+
private readonly sourceDebug;
|
|
327
|
+
/** Host-facing addresses (spec §6): scene gameId -> internal id (project-wide), and per-scene
|
|
328
|
+
* block gameId -> internal id. The effective gameId falls back to the name slug when unpinned. */
|
|
329
|
+
private readonly sceneGameIdToId;
|
|
330
|
+
private readonly blockGameIdToId;
|
|
331
|
+
/** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
|
|
332
|
+
* engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
|
|
333
|
+
private readonly creationOptions;
|
|
334
|
+
constructor(bundle: Bundle, options?: EngineOptions);
|
|
335
|
+
/** The active locale (string + character-name lookups resolve in it). */
|
|
336
|
+
get locale(): string;
|
|
337
|
+
/** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),
|
|
338
|
+
* not a shippable localised build. An IDs-only ship build is `false`. */
|
|
339
|
+
get isSourceDebug(): boolean;
|
|
340
|
+
/**
|
|
341
|
+
* Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
|
|
342
|
+
* string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
|
|
343
|
+
* locale; everything else - flow position, `@patter`/`@scene` state, visit counts, the PRNG - is
|
|
344
|
+
* untouched (already-emitted text isn't retro-translated; that's the host's call). A locale with no
|
|
345
|
+
* table resolves every string via the `<Untranslated: {id}>` source fallback. All open flows share the
|
|
346
|
+
* engine's string table, so the swap reaches every flow at once.
|
|
347
|
+
*/
|
|
348
|
+
setLocale(locale: string): void;
|
|
349
|
+
/**
|
|
350
|
+
* Live bundle refresh, tier 1 (strings only): swap every locale's string table in place from a
|
|
351
|
+
* freshly compiled bundle whose STRUCTURE is unchanged (same `content.structureHash`). Like
|
|
352
|
+
* setLocale, nothing restarts and no flow is touched: the next delivered beat reads the new text,
|
|
353
|
+
* `{@ref}` slots re-interpolate, and beats the host already received keep the words it saw. The
|
|
354
|
+
* swap reaches every open flow at once and is not part of save state. Structural edits need the
|
|
355
|
+
* full save/load hot swap instead (a structure change here simply won't show).
|
|
356
|
+
*/
|
|
357
|
+
replaceStrings(bundle: Bundle): void;
|
|
358
|
+
/**
|
|
359
|
+
* Live bundle refresh, tier 2 (full swap): rebuild on an edited bundle with the whole run carried
|
|
360
|
+
* over. Snapshot (`saveGame`), construct a fresh engine on `bundle` with THIS engine's original
|
|
361
|
+
* options (same world resolver, RNG, hooks), restore (`loadGame`), and carry over the presentation
|
|
362
|
+
* state that deliberately isn't save state (active locale, closed-captions toggle). The
|
|
363
|
+
* content-drift policy (§9.8) resolves edits under the cursor: stack frames re-find their next
|
|
364
|
+
* child by id, drifted options drop, a vanished snippet is skipped.
|
|
365
|
+
*
|
|
366
|
+
* Returns the REPLACEMENT engine; this one is left untouched and should be discarded. Hosts
|
|
367
|
+
* re-bind their flow handles via `next.getFlow(id)`. If the restore throws (defensive - §9.8
|
|
368
|
+
* makes this unreachable for ordinary edits), the swap falls back to a cold engine with each
|
|
369
|
+
* saved flow restarted from the top of the scene it was in.
|
|
370
|
+
*/
|
|
371
|
+
hotSwap(bundle: Bundle): Engine;
|
|
372
|
+
/** Whether closed captions are currently shown (full dialogue text). */
|
|
373
|
+
get closedCaptions(): boolean;
|
|
374
|
+
/**
|
|
375
|
+
* Turn closed captions on/off LIVE (#214). When OFF, subsequent dialogue lines have their caption
|
|
376
|
+
* cues (`[sigh]` etc., between the project's delimiters) and the surrounding whitespace stripped;
|
|
377
|
+
* narration, choice prompts, and everything else are untouched. Like setLocale this is a presentation
|
|
378
|
+
* toggle - it reaches every open flow at once and isn't part of save state; already-emitted text is
|
|
379
|
+
* not retro-edited. An IDs-only game applies the same rule itself via `flow.stripCaptions`.
|
|
380
|
+
*/
|
|
381
|
+
setClosedCaptions(on: boolean): void;
|
|
382
|
+
/**
|
|
383
|
+
* Open (and start) a named flow. Each flow has its own cursor, PRNG, and per-flow
|
|
384
|
+
* half of the scopes (not-shared `@patter`/`@scene`); all flows share the shared
|
|
385
|
+
* half. Re-opening an existing id replaces it with a fresh flow.
|
|
386
|
+
*/
|
|
387
|
+
openFlow(id: string, opts?: OpenFlowOptions): Flow;
|
|
388
|
+
/** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
|
|
389
|
+
private resolveSceneRef;
|
|
390
|
+
/** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
|
|
391
|
+
private resolveBlockRef;
|
|
392
|
+
/** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
|
|
393
|
+
* The inverse of the resolve helpers - for a host that wants to display / log the address. */
|
|
394
|
+
sceneAddress(sceneId: string): string | undefined;
|
|
395
|
+
blockAddress(blockId: string): string | undefined;
|
|
396
|
+
/**
|
|
397
|
+
* Author tags (#215) accumulated for a beat by id: its own tags unioned with every ancestor's
|
|
398
|
+
* (scene → block → group(s) → snippet → beat), deduped, outermost-first. The same value the beat's
|
|
399
|
+
* delivered step carries. Empty array for an unknown id or a beat with no tags anywhere up the chain.
|
|
400
|
+
*/
|
|
401
|
+
tagsForBeat(beatId: string): string[];
|
|
402
|
+
/** A scene's own tags (by internal id or gameId address). Empty when none / unknown. */
|
|
403
|
+
tagsForScene(sceneRef: string): string[];
|
|
404
|
+
/** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */
|
|
405
|
+
tagsForBlock(sceneRef: string, blockRef: string): string[];
|
|
406
|
+
/**
|
|
407
|
+
* The authored structure as a nested tree: scenes -> blocks -> children (groups + snippets, groups
|
|
408
|
+
* preserved) -> a snippet's beats. Static (no flow / play state); per-beat data is read at the source
|
|
409
|
+
* locale. For dev tooling that builds against the writer's structure (see also {@link getBeatSequence}).
|
|
410
|
+
*/
|
|
411
|
+
getOutline(): OutlineScene[];
|
|
412
|
+
/**
|
|
413
|
+
* Every beat in document order, flattened (through groups), each with the scene / block / snippet it
|
|
414
|
+
* belongs to and its static data. The linear view of {@link getOutline} - hand it to a tool that lays
|
|
415
|
+
* one item per beat (e.g. an Unreal Sequencer of subsequences).
|
|
416
|
+
*/
|
|
417
|
+
getBeatSequence(): FlatBeat[];
|
|
418
|
+
/** A node's outline entry: a group (selector + prompt + children) or a snippet (beats + jump). */
|
|
419
|
+
private outlineNode;
|
|
420
|
+
/** One beat's static data (source locale), the same shape a delivered step carries. */
|
|
421
|
+
private beatInfo;
|
|
422
|
+
/** A `{ tags }` fragment for an id, present only when the id has accumulated tags (keeps output tidy). */
|
|
423
|
+
private tagsField;
|
|
424
|
+
/** Retrieve an open flow by id (undefined if none / closed). */
|
|
425
|
+
getFlow(id: string): Flow | undefined;
|
|
426
|
+
/** All currently-open flows. */
|
|
427
|
+
flows(): Flow[];
|
|
428
|
+
/** Close (remove) a flow. */
|
|
429
|
+
closeFlow(id: string): void;
|
|
430
|
+
/**
|
|
431
|
+
* Reset the whole game to its initial state: drop every flow, re-seed the shared
|
|
432
|
+
* `@patter` globals to their declared defaults, and clear all shared state (shared
|
|
433
|
+
* `@scene` bags, world visit counts). World properties are host-owned and untouched.
|
|
434
|
+
* After reset, open fresh flows with `openFlow`.
|
|
435
|
+
*/
|
|
436
|
+
reset(): void;
|
|
437
|
+
/** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
|
|
438
|
+
getProperty(ref: string): ScalarValue | undefined;
|
|
439
|
+
/** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
|
|
440
|
+
setProperty(ref: string, value: ScalarValue): void;
|
|
441
|
+
/** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
|
|
442
|
+
* value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
|
|
443
|
+
listProperties(): PropertyRow[];
|
|
444
|
+
private splitShared;
|
|
445
|
+
/** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */
|
|
446
|
+
save(): EngineSave;
|
|
447
|
+
/** Restore shared `@patter` values (world properties untouched). */
|
|
448
|
+
load(blob: EngineSave): void;
|
|
449
|
+
/** Snapshot the whole game: shared `@patter` + visit counts + every live flow. */
|
|
450
|
+
saveGame(): SaveGame;
|
|
451
|
+
/** Restore a `saveGame()`: shared globals + visit counts + shared scene bags + reconstruct every flow. */
|
|
452
|
+
loadGame(save: SaveGame): void;
|
|
453
|
+
}
|
|
454
|
+
declare class Flow {
|
|
455
|
+
readonly id: string;
|
|
456
|
+
private readonly host;
|
|
457
|
+
private local;
|
|
458
|
+
private rngState;
|
|
459
|
+
private started;
|
|
460
|
+
private flowEnded;
|
|
461
|
+
private currentSceneId;
|
|
462
|
+
private stack;
|
|
463
|
+
private activeSnippet;
|
|
464
|
+
private beatIndex;
|
|
465
|
+
private pendingChoice;
|
|
466
|
+
/** When `replayPromptOnChoose`, the chosen option's prompt beat to deliver before its content. */
|
|
467
|
+
private pendingPromptBeat;
|
|
468
|
+
/** The chosen option that owns `pendingPromptBeat`, so a save taken between choose() and the next
|
|
469
|
+
* advance() can re-derive the prompt on load (the beat isn't otherwise reachable by id). */
|
|
470
|
+
private pendingPromptOwnerId;
|
|
471
|
+
private selectors;
|
|
472
|
+
/** Per-node entry counts for this flow (node id -> times entered). */
|
|
473
|
+
private visitCounts;
|
|
474
|
+
private sceneBags;
|
|
475
|
+
private readonly patterResolver;
|
|
476
|
+
private readonly sceneResolver;
|
|
477
|
+
private readonly evalCtx;
|
|
478
|
+
constructor(id: string, host: FlowHost, seed: number);
|
|
479
|
+
/** Begin this flow at a scene (and optionally a specific block within it). */
|
|
480
|
+
start(sceneId?: string, blockId?: string): void;
|
|
481
|
+
/**
|
|
482
|
+
* Forget everything in this flow and begin again - its per-flow state (not-shared
|
|
483
|
+
* `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
|
|
484
|
+
* visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
|
|
485
|
+
* untouched. A clearer-named alias of `start()`.
|
|
486
|
+
*/
|
|
487
|
+
reset(sceneId?: string, blockId?: string): void;
|
|
488
|
+
/** The scene the cursor is currently in - set on entry and whenever a jump crosses scenes. Read
|
|
489
|
+
* right after `advance()` to know which scene the just-played beat lives in (tooling that mirrors
|
|
490
|
+
* the playhead, e.g. an editor following a cross-scene jump). `null` before the flow has started. */
|
|
491
|
+
get currentScene(): string | null;
|
|
492
|
+
/** Run until the next line, game event, choice, or the end of the flow. */
|
|
493
|
+
advance(): StepResult;
|
|
494
|
+
/**
|
|
495
|
+
* Advance repeatedly, collecting every played beat, until a choice or the end - the "play to the
|
|
496
|
+
* next stop" a host's play UI / tooling wants. The terminal `choice` / `end` is returned as `stop`;
|
|
497
|
+
* `played` holds the line / text / game-event results walked on the way to it. Termination is guaranteed
|
|
498
|
+
* (each `advance()` makes progress or `settle()` throws on a contentless jump cycle).
|
|
499
|
+
*/
|
|
500
|
+
advanceToStop(): AdvanceToStopResult;
|
|
501
|
+
/**
|
|
502
|
+
* Drive the cursor to the next *deliverable* stop: a beat ready on the active
|
|
503
|
+
* snippet, a pending choice, or the end. Runs onExit/jump seams and walks the
|
|
504
|
+
* block run (sequentially, skipping ineligible children); a finished block pops
|
|
505
|
+
* to its caller (call-return) or ends the flow.
|
|
506
|
+
*/
|
|
507
|
+
private settle;
|
|
508
|
+
/** The options of a pending choice (empty when not at a choice point). */
|
|
509
|
+
getChoices(): ChoiceOption[];
|
|
510
|
+
/** Pick an eligible option by id; the next `advance()` runs it. */
|
|
511
|
+
choose(id: string): void;
|
|
512
|
+
isEnded(): boolean;
|
|
513
|
+
/** Read a property by ref - `@patter` / `@scene` (each routed by its `shared` flag) or foreign. */
|
|
514
|
+
getProperty(ref: string): ScalarValue | undefined;
|
|
515
|
+
/** Write a property by ref (routed by scope, then by the property's `shared` flag). */
|
|
516
|
+
setProperty(ref: string, value: ScalarValue): void;
|
|
517
|
+
/** @internal Snapshot this flow's cursor + per-flow scopes (not-shared `@patter`/`@scene`) + PRNG. */
|
|
518
|
+
snapshot(): FlowSnapshot;
|
|
519
|
+
/** @internal Restore this flow from a snapshot. */
|
|
520
|
+
restore(snap: FlowSnapshot): void;
|
|
521
|
+
/** Set the current scene, reset its scene-local props, run onEntry. */
|
|
522
|
+
private enterSceneSetup;
|
|
523
|
+
/**
|
|
524
|
+
* Play one child of the active run. A snippet begins delivering. A group is
|
|
525
|
+
* walked by its selector: the default `run` pushes a nested run (its children
|
|
526
|
+
* play in order, gathering back); `choice` stops for the host; a select-one
|
|
527
|
+
* selector (branch, or a `sequence` in any order x exhaust mode) picks ONE child (recursing
|
|
528
|
+
* to a leaf) - selecting nothing contributes no content and the run continues.
|
|
529
|
+
*/
|
|
530
|
+
private enterChild;
|
|
531
|
+
/** A container's children, whether it's a block or a run-group; undefined if the id is gone. */
|
|
532
|
+
private childrenOf;
|
|
533
|
+
private beginSnippet;
|
|
534
|
+
private setupChoice;
|
|
535
|
+
private resolveJump;
|
|
536
|
+
/**
|
|
537
|
+
* Route to a target (scene / block / `END`). `call` PUSHES a return frame (the
|
|
538
|
+
* caller's block run, already advanced to its next child, stays below); `jump`
|
|
539
|
+
* is absolute - it REPLACES the whole stack, discarding pending returns. `END`
|
|
540
|
+
* hard-ends the flow regardless of the callstack.
|
|
541
|
+
*/
|
|
542
|
+
private enterTarget;
|
|
543
|
+
private selectChild;
|
|
544
|
+
/** `sequence` with `order: "sequential"` - walk children in authored order. */
|
|
545
|
+
private pickSequential;
|
|
546
|
+
/**
|
|
547
|
+
* `sequence` with `order: "shuffle"` - draw WITHOUT replacement (a bag), never
|
|
548
|
+
* repeating the immediately-previous pick across a reshuffle (no line twice in a
|
|
549
|
+
* row when >=2 are eligible). `stick` holds out the last authored child as the
|
|
550
|
+
* permanent terminal; `once` stops after one pass; `repeat` reshuffles.
|
|
551
|
+
*/
|
|
552
|
+
private pickShuffle;
|
|
553
|
+
/** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
|
|
554
|
+
private selectorState;
|
|
555
|
+
private runEffects;
|
|
556
|
+
private eligible;
|
|
557
|
+
private evalExpr;
|
|
558
|
+
/** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
|
|
559
|
+
private enter;
|
|
560
|
+
/** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32. */
|
|
561
|
+
private readonly rng;
|
|
562
|
+
private beatResult;
|
|
563
|
+
/**
|
|
564
|
+
* Expand inline `{@ref}` slots (spec §16) against this flow's CURRENT property state. Public so an
|
|
565
|
+
* IDs-only game can apply the same property replacement to a string it looked up in its own loc system:
|
|
566
|
+
* the engine handed it the beat ID, the game fetched its translation, then calls `flow.interpolate(...)`.
|
|
567
|
+
*/
|
|
568
|
+
interpolate(raw: string): string;
|
|
569
|
+
/**
|
|
570
|
+
* Apply the project's caption rule to a string UNCONDITIONALLY (#214): remove every cue span between
|
|
571
|
+
* the project's delimiters and collapse the whitespace. Public so an IDs-only game - which looks up
|
|
572
|
+
* its own strings - can match the embedded runtime: `flow.stripCaptions(flow.interpolate(text))` when
|
|
573
|
+
* its own captions setting is off. (Embedded play does this automatically for dialogue lines.)
|
|
574
|
+
*/
|
|
575
|
+
stripCaptions(raw: string): string;
|
|
576
|
+
/** Caption-strip a dialogue line ONLY when captions are off; otherwise pass the text through. The
|
|
577
|
+
* internal gate the engine applies to every `line` beat / line-kind prompt. */
|
|
578
|
+
private captionLine;
|
|
579
|
+
/**
|
|
580
|
+
* An option's prompt (spec §5): the Option group's `prompt` beat, resolved + interpolated
|
|
581
|
+
* (choice labels are on-screen text, so they interpolate, spec §16). For the degenerate
|
|
582
|
+
* bare-snippet tolerance - or an Option group authored without a prompt - it falls back to the
|
|
583
|
+
* option's first content line. NO look-ahead. Undefined only when even that is absent.
|
|
584
|
+
*/
|
|
585
|
+
private promptFor;
|
|
586
|
+
/** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */
|
|
587
|
+
private promptBeatOf;
|
|
588
|
+
/** The first snippet with a line/text beat within a child list, depth-first in authored order. */
|
|
589
|
+
private firstTextSnippetIn;
|
|
590
|
+
private resolveString;
|
|
591
|
+
/** A character's player-facing name: the `cast:<name>` string in the active locale, else the default
|
|
592
|
+
* locale, else the authoring `displayName`. Undefined when the character has no display name at all
|
|
593
|
+
* (the host falls back to the `character` token itself). */
|
|
594
|
+
private resolveCharacterName;
|
|
595
|
+
/** Split a ref into scope + name. Tokens: `@scene`, foreign tokens, else `@patter` (incl. bare `@name`). */
|
|
596
|
+
private splitRef;
|
|
597
|
+
/** The per-flow registry: the NOT-shared `@patter` globals (the shared ones live on the host). */
|
|
598
|
+
private freshLocal;
|
|
599
|
+
/**
|
|
600
|
+
* Seed a scene's `@scene` props (spec §7). The not-shared props seed THIS flow's
|
|
601
|
+
* bag the first time it enters (persist across re-entries thereafter); the shared
|
|
602
|
+
* props seed the host's stage bag the first time ANY flow enters the scene (shared
|
|
603
|
+
* and persistent thereafter - a later flow finds it present and leaves it).
|
|
604
|
+
* `temporary` props are the exception: reseeded to their default on every entry.
|
|
605
|
+
*/
|
|
606
|
+
private seedScene;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** The author-defined gameData fields declared for a node TYPE in a bundle (empty when none). */
|
|
610
|
+
declare function gameDataFields(bundle: Bundle, kind: GameDataNodeKind): GameDataField[];
|
|
611
|
+
/** One node's effective value for a field: its sparse OVERRIDE if present, else the field's declared
|
|
612
|
+
* default (undefined if neither is set). `fields` is the schema for the node's type. */
|
|
613
|
+
declare function gameDataValue(fields: GameDataField[], node: GameData | undefined, name: string): unknown;
|
|
614
|
+
/** A node's FULL effective gameData: every declared field resolved (override or default), plus any
|
|
615
|
+
* override keys with no matching field (orphans, kept verbatim). Fields left with no value are omitted. */
|
|
616
|
+
declare function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData;
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Map every node id (scene / block / group / snippet / beat) to its accumulated
|
|
620
|
+
* tags. Node ids are globally unique within a project (the validator enforces
|
|
621
|
+
* it), so one flat map suffices. Nodes with no tags anywhere up the chain map to
|
|
622
|
+
* an empty array.
|
|
623
|
+
*/
|
|
624
|
+
declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
|
|
625
|
+
|
|
626
|
+
export { type AdvanceToStopResult, type BeatInfo, type ChoiceOption, Engine, type EngineOptions, type EngineSave, type FlatBeat, Flow, type FlowSnapshot, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type PropertyRow, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type WorldResolver, buildTagIndex, effectiveGameData, gameDataFields, gameDataValue };
|