@patterkit/model 0.17.0 → 0.19.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.cjs CHANGED
@@ -57,13 +57,17 @@ __export(index_exports, {
57
57
  gameIdify: () => gameIdify,
58
58
  isCaseOnlyPropertyName: () => import_expr.isCaseOnlyPropertyName,
59
59
  isContentlessBeat: () => isContentlessBeat,
60
+ isDialogueScene: () => isDialogueScene,
60
61
  isValidGameId: () => isValidGameId,
61
62
  isValidPropertyName: () => import_expr.isValidPropertyName,
62
63
  projectLayout: () => projectLayout,
63
64
  projectQualifiers: () => projectQualifiers,
64
65
  propertyNameify: () => import_expr.propertyNameify,
65
66
  qualifierStringKey: () => qualifierStringKey,
67
+ resolveSceneRef: () => resolveSceneRef,
68
+ sceneAddressIndex: () => sceneAddressIndex,
66
69
  speakerLabelKey: () => speakerLabelKey,
70
+ textIsAction: () => textIsAction,
67
71
  usedQualifiers: () => usedQualifiers,
68
72
  usedSpeakerPairs: () => usedSpeakerPairs,
69
73
  walkNodes: () => walkNodes
@@ -94,6 +98,14 @@ function effectiveGameId(entity) {
94
98
  const g = entity.gameId?.trim();
95
99
  return g ? g : gameIdify(entity.name);
96
100
  }
101
+ function sceneAddressIndex(scenes) {
102
+ const byAddress = /* @__PURE__ */ new Map();
103
+ for (const [id, scene] of Object.entries(scenes)) byAddress.set(effectiveGameId(scene), id);
104
+ return byAddress;
105
+ }
106
+ function resolveSceneRef(scenes, ref, byAddress) {
107
+ return (byAddress ?? sceneAddressIndex(scenes)).get(ref) ?? (Object.hasOwn(scenes, ref) ? ref : void 0);
108
+ }
97
109
  var COMMON_GENDERS = ["male", "female", "neuter"];
98
110
  var DEFAULT_QUALIFIERS = [
99
111
  { gameId: "vo", name: "V.O.", description: "Voice-over: the character isn't in the scene (a thought, narration, a voice in the ear)." },
@@ -141,6 +153,27 @@ function usedSpeakerPairs(scenes) {
141
153
  }
142
154
  return out;
143
155
  }
156
+ function isDialogueScene(scene) {
157
+ let found = false;
158
+ for (const block of scene.blocks) {
159
+ walkNodes(block.children, (node) => {
160
+ if (found) return;
161
+ if (node.type === "group") {
162
+ if (node.prompt?.kind === "line" && node.prompt.character) found = true;
163
+ return;
164
+ }
165
+ for (const beat of node.beats ?? []) if (beat.kind === "line" && beat.character) {
166
+ found = true;
167
+ return;
168
+ }
169
+ });
170
+ if (found) return true;
171
+ }
172
+ return false;
173
+ }
174
+ function textIsAction(project, scene) {
175
+ return !project.localiseDialogueText && isDialogueScene(scene);
176
+ }
144
177
  var DEFAULT_PAD_AFTER = 0.6;
145
178
  var PAD_AFTER_MIN = -10;
146
179
  var PAD_AFTER_MAX = 60;
@@ -258,13 +291,17 @@ var UNRESOLVED_KEY = "unresolved";
258
291
  gameIdify,
259
292
  isCaseOnlyPropertyName,
260
293
  isContentlessBeat,
294
+ isDialogueScene,
261
295
  isValidGameId,
262
296
  isValidPropertyName,
263
297
  projectLayout,
264
298
  projectQualifiers,
265
299
  propertyNameify,
266
300
  qualifierStringKey,
301
+ resolveSceneRef,
302
+ sceneAddressIndex,
267
303
  speakerLabelKey,
304
+ textIsAction,
268
305
  usedQualifiers,
269
306
  usedSpeakerPairs,
270
307
  walkNodes
package/dist/index.d.cts CHANGED
@@ -43,7 +43,7 @@ interface LineBeat {
43
43
  tags?: string[];
44
44
  }
45
45
  /**
46
- * Authorial voice / narration - speaker-less prose (spec §2). Never voiced; its
46
+ * Text - speaker-less prose (spec §2). Never voiced; its
47
47
  * localised text always permits inline `{@name}` interpolation (spec §16). This
48
48
  * is the on-screen-text role (e.g. "A door slams!") - distinct from a game-event
49
49
  * beat, which is a pure engine instruction with no localised content.
@@ -63,6 +63,11 @@ interface TextBeat {
63
63
  extensions?: NodeExtensions;
64
64
  /** Author tags (#215). See LineBeat.tags. */
65
65
  tags?: string[];
66
+ /** Bundle only: this text beat is ACTION, a direction to the people staging the scene that the player
67
+ * never sees (design/proposals/action-text.md). Set by the compiler on every text beat of a dialogue scene
68
+ * (`isDialogueScene`) when the project's `localiseDialogueText` is off; its text is then the source in
69
+ * every locale. Absent = shown text. Never in source. */
70
+ action?: true;
66
71
  }
67
72
  /**
68
73
  * A GAME EVENT (spec §2): a pure engine instruction with no player-facing text - just `gameData` the host
@@ -252,6 +257,25 @@ declare function effectiveGameId(entity: {
252
257
  gameId?: string;
253
258
  name: string;
254
259
  }): string;
260
+ /** A bundle's scenes by their address (effective gameId), for `resolveSceneRef` to look up in. */
261
+ declare function sceneAddressIndex(scenes: Readonly<Record<string, {
262
+ gameId?: string;
263
+ name: string;
264
+ }>>): Map<string, string>;
265
+ /**
266
+ * THE rule for resolving a scene reference to its internal id: its address (the effective gameId, the
267
+ * host-facing name, spec §6) first, then its internal id. Undefined when neither names a scene.
268
+ *
269
+ * One rule for the runtime's openFlow, goto and every engine lookup, and for every tool that has to land
270
+ * where the game will (the Storylet Engine's Board and pairing check, Patterpad's Show Card). Each used to
271
+ * keep a copy, and the copies tried the internal id first, so where one scene's id was another's gameId
272
+ * they picked a different scene from the one the game opened (family parity item 1). Pass `byAddress`
273
+ * (from `sceneAddressIndex`) to look up many references in one bundle without rebuilding it.
274
+ */
275
+ declare function resolveSceneRef(scenes: Readonly<Record<string, {
276
+ gameId?: string;
277
+ name: string;
278
+ }>>, ref: string, byAddress?: ReadonlyMap<string, string>): string | undefined;
255
279
  type PropertyType = "boolean" | "number" | "string" | "flags" | "enum" | "quality";
256
280
  interface PropertyDecl {
257
281
  name: string;
@@ -394,6 +418,12 @@ declare function usedSpeakerPairs(scenes: Scene[]): {
394
418
  character: string;
395
419
  qualifier: string;
396
420
  }[];
421
+ /** A DIALOGUE scene (design/proposals/action-text.md): one with at least one line beat that names a
422
+ * character, an option's prompt included. Any other scene is a TEXT scene. */
423
+ declare function isDialogueScene(scene: Scene): boolean;
424
+ /** Whether a scene's text beats are ACTION: a dialogue scene, in a project that doesn't localise dialogue
425
+ * scenes' text (`localiseDialogueText` off, the default). Its option prompts never are. */
426
+ declare function textIsAction(project: Pick<ProjectFile, "localiseDialogueText">, scene: Scene): boolean;
397
427
  /** The built-in `padAfter`, in seconds, for a project that sets no default of its own: the same on every runtime
398
428
  * and in Patterpad's Play window (design/proposals/line-padding.md section 4). */
399
429
  declare const DEFAULT_PAD_AFTER = 0.6;
@@ -412,7 +442,7 @@ interface BundleQualifier {
412
442
  * the bundle; this type states the resulting contract for anyone reading a bundle. */
413
443
  type BundleCastMember = Omit<CastMember, "notes" | "actor" | "gender">;
414
444
  /** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).
415
- * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`.
445
+ * Beat kinds map straight through: dialogue = `line`, text = `text`, game event = `gameEvent`.
416
446
  * `option` is a choice's option: a group that is a direct child of a `choice` group, whose gameData the
417
447
  * host reads off the option `getChoices` hands it. Any other group's gameData stays undeclared. */
418
448
  type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent" | "option";
@@ -589,7 +619,7 @@ interface ProjectFile {
589
619
  * opt-in even for a voiced project (a voiced story may want voice scripts without tracking recording status). */
590
620
  trackAudioStatus?: boolean;
591
621
  /**
592
- * Inline text formatting. When true, authors can mark dialogue / narration / direction /
622
+ * Inline text formatting. When true, authors can mark dialogue / text / direction /
593
623
  * choice-prompt text bold, italic, or bold+italic; it is stored INSIDE the localised strings
594
624
  * (and the flow's `direction`) as `<b>…</b>`, `<i>…</i>`, `<bi>…</bi>`, with literal `<`, `>`,
595
625
  * `&` escaped to `&lt;` / `&gt;` / `&amp;`. The runtime treats the string as opaque - it is the
@@ -662,6 +692,11 @@ interface ProjectFile {
662
692
  /** The project's default `padAfter` for every line and text beat that sets none and has no default above it.
663
693
  * Absent = `DEFAULT_PAD_AFTER`. */
664
694
  padAfterDefault?: number;
695
+ /** Localise text lines in dialogue scenes (design/proposals/action-text.md). Off (absent) by default: a
696
+ * dialogue scene's text beats (`isDialogueScene`) are then ACTION, left out of the loc files and the
697
+ * written word counts, and compiled with `action: true`. On: they're localised and shown as a text
698
+ * scene's are. A text scene is localised whatever this says, and an option's prompt always is. */
699
+ localiseDialogueText?: boolean;
665
700
  gameDataFields?: GameDataFields;
666
701
  /**
667
702
  * The Patter extensions this project takes, by id (ruled 2026-10-18: the project opts in), each pinned to a
@@ -707,6 +742,11 @@ interface LocaleFile {
707
742
  scene: string;
708
743
  locale: string;
709
744
  default?: boolean;
745
+ /** A translated shard only: its scene's text beats were action (`textIsAction`) when loc import last wrote
746
+ * it, so they aren't in it. Loc export and `patter validate` compare this with the scene now, to report
747
+ * text that has dropped out of the loc files or come into them. Absent = shown text, or a shard written
748
+ * before the setting existed. */
749
+ textAction?: true;
710
750
  /** beatId -> text. */
711
751
  strings: Record<string, string>;
712
752
  }
@@ -919,7 +959,7 @@ interface HandoffFile {
919
959
  }
920
960
  /** An editable line as sent. */
921
961
  interface HandoffLine {
922
- /** The beat id (a line, a narration beat, or an option prompt). */
962
+ /** The beat id (a line, a text beat, or an option prompt). */
923
963
  id: string;
924
964
  kind: "line" | "narration" | "option";
925
965
  /** The speaker, speaker qualifier (`gameId`), and direction as sent (spoken lines only). */
@@ -1362,4 +1402,4 @@ interface ExtensionFieldDecl {
1362
1402
  /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1363
1403
  type NodeExtensions = Record<string, Record<string, unknown>>;
1364
1404
 
1365
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowSnapshotV2, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV2, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, speakerLabelKey, usedQualifiers, usedSpeakerPairs, walkNodes };
1405
+ export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowSnapshotV2, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV2, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isDialogueScene, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
package/dist/index.d.ts CHANGED
@@ -43,7 +43,7 @@ interface LineBeat {
43
43
  tags?: string[];
44
44
  }
45
45
  /**
46
- * Authorial voice / narration - speaker-less prose (spec §2). Never voiced; its
46
+ * Text - speaker-less prose (spec §2). Never voiced; its
47
47
  * localised text always permits inline `{@name}` interpolation (spec §16). This
48
48
  * is the on-screen-text role (e.g. "A door slams!") - distinct from a game-event
49
49
  * beat, which is a pure engine instruction with no localised content.
@@ -63,6 +63,11 @@ interface TextBeat {
63
63
  extensions?: NodeExtensions;
64
64
  /** Author tags (#215). See LineBeat.tags. */
65
65
  tags?: string[];
66
+ /** Bundle only: this text beat is ACTION, a direction to the people staging the scene that the player
67
+ * never sees (design/proposals/action-text.md). Set by the compiler on every text beat of a dialogue scene
68
+ * (`isDialogueScene`) when the project's `localiseDialogueText` is off; its text is then the source in
69
+ * every locale. Absent = shown text. Never in source. */
70
+ action?: true;
66
71
  }
67
72
  /**
68
73
  * A GAME EVENT (spec §2): a pure engine instruction with no player-facing text - just `gameData` the host
@@ -252,6 +257,25 @@ declare function effectiveGameId(entity: {
252
257
  gameId?: string;
253
258
  name: string;
254
259
  }): string;
260
+ /** A bundle's scenes by their address (effective gameId), for `resolveSceneRef` to look up in. */
261
+ declare function sceneAddressIndex(scenes: Readonly<Record<string, {
262
+ gameId?: string;
263
+ name: string;
264
+ }>>): Map<string, string>;
265
+ /**
266
+ * THE rule for resolving a scene reference to its internal id: its address (the effective gameId, the
267
+ * host-facing name, spec §6) first, then its internal id. Undefined when neither names a scene.
268
+ *
269
+ * One rule for the runtime's openFlow, goto and every engine lookup, and for every tool that has to land
270
+ * where the game will (the Storylet Engine's Board and pairing check, Patterpad's Show Card). Each used to
271
+ * keep a copy, and the copies tried the internal id first, so where one scene's id was another's gameId
272
+ * they picked a different scene from the one the game opened (family parity item 1). Pass `byAddress`
273
+ * (from `sceneAddressIndex`) to look up many references in one bundle without rebuilding it.
274
+ */
275
+ declare function resolveSceneRef(scenes: Readonly<Record<string, {
276
+ gameId?: string;
277
+ name: string;
278
+ }>>, ref: string, byAddress?: ReadonlyMap<string, string>): string | undefined;
255
279
  type PropertyType = "boolean" | "number" | "string" | "flags" | "enum" | "quality";
256
280
  interface PropertyDecl {
257
281
  name: string;
@@ -394,6 +418,12 @@ declare function usedSpeakerPairs(scenes: Scene[]): {
394
418
  character: string;
395
419
  qualifier: string;
396
420
  }[];
421
+ /** A DIALOGUE scene (design/proposals/action-text.md): one with at least one line beat that names a
422
+ * character, an option's prompt included. Any other scene is a TEXT scene. */
423
+ declare function isDialogueScene(scene: Scene): boolean;
424
+ /** Whether a scene's text beats are ACTION: a dialogue scene, in a project that doesn't localise dialogue
425
+ * scenes' text (`localiseDialogueText` off, the default). Its option prompts never are. */
426
+ declare function textIsAction(project: Pick<ProjectFile, "localiseDialogueText">, scene: Scene): boolean;
397
427
  /** The built-in `padAfter`, in seconds, for a project that sets no default of its own: the same on every runtime
398
428
  * and in Patterpad's Play window (design/proposals/line-padding.md section 4). */
399
429
  declare const DEFAULT_PAD_AFTER = 0.6;
@@ -412,7 +442,7 @@ interface BundleQualifier {
412
442
  * the bundle; this type states the resulting contract for anyone reading a bundle. */
413
443
  type BundleCastMember = Omit<CastMember, "notes" | "actor" | "gender">;
414
444
  /** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).
415
- * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`.
445
+ * Beat kinds map straight through: dialogue = `line`, text = `text`, game event = `gameEvent`.
416
446
  * `option` is a choice's option: a group that is a direct child of a `choice` group, whose gameData the
417
447
  * host reads off the option `getChoices` hands it. Any other group's gameData stays undeclared. */
418
448
  type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent" | "option";
@@ -589,7 +619,7 @@ interface ProjectFile {
589
619
  * opt-in even for a voiced project (a voiced story may want voice scripts without tracking recording status). */
590
620
  trackAudioStatus?: boolean;
591
621
  /**
592
- * Inline text formatting. When true, authors can mark dialogue / narration / direction /
622
+ * Inline text formatting. When true, authors can mark dialogue / text / direction /
593
623
  * choice-prompt text bold, italic, or bold+italic; it is stored INSIDE the localised strings
594
624
  * (and the flow's `direction`) as `<b>…</b>`, `<i>…</i>`, `<bi>…</bi>`, with literal `<`, `>`,
595
625
  * `&` escaped to `&lt;` / `&gt;` / `&amp;`. The runtime treats the string as opaque - it is the
@@ -662,6 +692,11 @@ interface ProjectFile {
662
692
  /** The project's default `padAfter` for every line and text beat that sets none and has no default above it.
663
693
  * Absent = `DEFAULT_PAD_AFTER`. */
664
694
  padAfterDefault?: number;
695
+ /** Localise text lines in dialogue scenes (design/proposals/action-text.md). Off (absent) by default: a
696
+ * dialogue scene's text beats (`isDialogueScene`) are then ACTION, left out of the loc files and the
697
+ * written word counts, and compiled with `action: true`. On: they're localised and shown as a text
698
+ * scene's are. A text scene is localised whatever this says, and an option's prompt always is. */
699
+ localiseDialogueText?: boolean;
665
700
  gameDataFields?: GameDataFields;
666
701
  /**
667
702
  * The Patter extensions this project takes, by id (ruled 2026-10-18: the project opts in), each pinned to a
@@ -707,6 +742,11 @@ interface LocaleFile {
707
742
  scene: string;
708
743
  locale: string;
709
744
  default?: boolean;
745
+ /** A translated shard only: its scene's text beats were action (`textIsAction`) when loc import last wrote
746
+ * it, so they aren't in it. Loc export and `patter validate` compare this with the scene now, to report
747
+ * text that has dropped out of the loc files or come into them. Absent = shown text, or a shard written
748
+ * before the setting existed. */
749
+ textAction?: true;
710
750
  /** beatId -> text. */
711
751
  strings: Record<string, string>;
712
752
  }
@@ -919,7 +959,7 @@ interface HandoffFile {
919
959
  }
920
960
  /** An editable line as sent. */
921
961
  interface HandoffLine {
922
- /** The beat id (a line, a narration beat, or an option prompt). */
962
+ /** The beat id (a line, a text beat, or an option prompt). */
923
963
  id: string;
924
964
  kind: "line" | "narration" | "option";
925
965
  /** The speaker, speaker qualifier (`gameId`), and direction as sent (spoken lines only). */
@@ -1362,4 +1402,4 @@ interface ExtensionFieldDecl {
1362
1402
  /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1363
1403
  type NodeExtensions = Record<string, Record<string, unknown>>;
1364
1404
 
1365
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowSnapshotV2, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV2, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, speakerLabelKey, usedQualifiers, usedSpeakerPairs, walkNodes };
1405
+ export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowSnapshotV2, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV2, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isDialogueScene, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
package/dist/index.js CHANGED
@@ -29,6 +29,14 @@ function effectiveGameId(entity) {
29
29
  const g = entity.gameId?.trim();
30
30
  return g ? g : gameIdify(entity.name);
31
31
  }
32
+ function sceneAddressIndex(scenes) {
33
+ const byAddress = /* @__PURE__ */ new Map();
34
+ for (const [id, scene] of Object.entries(scenes)) byAddress.set(effectiveGameId(scene), id);
35
+ return byAddress;
36
+ }
37
+ function resolveSceneRef(scenes, ref, byAddress) {
38
+ return (byAddress ?? sceneAddressIndex(scenes)).get(ref) ?? (Object.hasOwn(scenes, ref) ? ref : void 0);
39
+ }
32
40
  var COMMON_GENDERS = ["male", "female", "neuter"];
33
41
  var DEFAULT_QUALIFIERS = [
34
42
  { gameId: "vo", name: "V.O.", description: "Voice-over: the character isn't in the scene (a thought, narration, a voice in the ear)." },
@@ -76,6 +84,27 @@ function usedSpeakerPairs(scenes) {
76
84
  }
77
85
  return out;
78
86
  }
87
+ function isDialogueScene(scene) {
88
+ let found = false;
89
+ for (const block of scene.blocks) {
90
+ walkNodes(block.children, (node) => {
91
+ if (found) return;
92
+ if (node.type === "group") {
93
+ if (node.prompt?.kind === "line" && node.prompt.character) found = true;
94
+ return;
95
+ }
96
+ for (const beat of node.beats ?? []) if (beat.kind === "line" && beat.character) {
97
+ found = true;
98
+ return;
99
+ }
100
+ });
101
+ if (found) return true;
102
+ }
103
+ return false;
104
+ }
105
+ function textIsAction(project, scene) {
106
+ return !project.localiseDialogueText && isDialogueScene(scene);
107
+ }
79
108
  var DEFAULT_PAD_AFTER = 0.6;
80
109
  var PAD_AFTER_MIN = -10;
81
110
  var PAD_AFTER_MAX = 60;
@@ -192,13 +221,17 @@ export {
192
221
  gameIdify,
193
222
  isCaseOnlyPropertyName,
194
223
  isContentlessBeat,
224
+ isDialogueScene,
195
225
  isValidGameId,
196
226
  isValidPropertyName,
197
227
  projectLayout,
198
228
  projectQualifiers,
199
229
  propertyNameify,
200
230
  qualifierStringKey,
231
+ resolveSceneRef,
232
+ sceneAddressIndex,
201
233
  speakerLabelKey,
234
+ textIsAction,
202
235
  usedQualifiers,
203
236
  usedSpeakerPairs,
204
237
  walkNodes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/model",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Patter data-model types: source (scene/block/group/snippet/beat/jump), project, locale, authoring. The shape source-of-truth.",
5
5
  "type": "module",
6
6
  "license": "MIT",