@patterkit/model 0.15.0 → 0.17.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
@@ -31,10 +31,15 @@ __export(index_exports, {
31
31
  DEFAULT_RECORDING_STATUSES: () => DEFAULT_RECORDING_STATUSES,
32
32
  DEFAULT_SPEAKER_PATTERN: () => DEFAULT_SPEAKER_PATTERN,
33
33
  DEFAULT_WRITING_STATUSES: () => DEFAULT_WRITING_STATUSES,
34
+ EXTENSIONS_DIR: () => EXTENSIONS_DIR,
35
+ EXTENSION_SCHEMA: () => EXTENSION_SCHEMA,
36
+ EXTENSION_SHARD: () => EXTENSION_SHARD,
34
37
  FLOW_SCHEMA: () => FLOW_SCHEMA,
35
38
  HANDOFF_SCHEMA: () => HANDOFF_SCHEMA,
39
+ MANIFEST_EXTENSION: () => MANIFEST_EXTENSION,
36
40
  PAD_AFTER_MAX: () => PAD_AFTER_MAX,
37
41
  PAD_AFTER_MIN: () => PAD_AFTER_MIN,
42
+ PROJECT_EXTENSION_STEM: () => PROJECT_EXTENSION_STEM,
38
43
  PROJECT_LOCALE_SCENE: () => PROJECT_LOCALE_SCENE,
39
44
  PROJECT_SCHEMA: () => PROJECT_SCHEMA,
40
45
  RERECORD_STATUS: () => RERECORD_STATUS,
@@ -44,6 +49,7 @@ __export(index_exports, {
44
49
  SAVE_VERSION: () => SAVE_VERSION,
45
50
  SPEAKER_PATTERN_KEY: () => SPEAKER_PATTERN_KEY,
46
51
  STRINGS_SCHEMA: () => STRINGS_SCHEMA,
52
+ UNRESOLVED_KEY: () => UNRESOLVED_KEY,
47
53
  castStringKey: () => castStringKey,
48
54
  deriveRecordingFolders: () => deriveRecordingFolders,
49
55
  effectiveGameId: () => effectiveGameId,
@@ -199,7 +205,7 @@ var DEFAULT_CAPTION_DELIMITERS = { open: "[", close: "]" };
199
205
  var DEFAULT_CAPTION_CHARACTER = "SFX";
200
206
  var SAVE_SCHEMA = "patter/save@0";
201
207
  var SAVE_VERSION = 3;
202
- var DEFAULT_LAYOUT = { flow: "scenes/", strings: "loc/", authoring: "authoring/" };
208
+ var DEFAULT_LAYOUT = { flow: "scenes/", strings: "loc/", authoring: "authoring/", extensions: "extensions/" };
203
209
  function projectLayout(project) {
204
210
  return { ...DEFAULT_LAYOUT, ...project.layout };
205
211
  }
@@ -207,6 +213,12 @@ var FLOW_SCHEMA = "patter/flow@0";
207
213
  var PROJECT_SCHEMA = "patter/project@0";
208
214
  var STRINGS_SCHEMA = "patter/strings@0";
209
215
  var AUTHORING_SCHEMA = "patter/authoring@0";
216
+ var EXTENSION_SCHEMA = "patter/extension@0";
217
+ var EXTENSION_SHARD = ".patterextension";
218
+ var PROJECT_EXTENSION_STEM = "_project";
219
+ var MANIFEST_EXTENSION = ".extension.json";
220
+ var EXTENSIONS_DIR = "extensions";
221
+ var UNRESOLVED_KEY = "unresolved";
210
222
  // Annotate the CommonJS export names for ESM import in node:
211
223
  0 && (module.exports = {
212
224
  AUTHORING_SCHEMA,
@@ -220,10 +232,15 @@ var AUTHORING_SCHEMA = "patter/authoring@0";
220
232
  DEFAULT_RECORDING_STATUSES,
221
233
  DEFAULT_SPEAKER_PATTERN,
222
234
  DEFAULT_WRITING_STATUSES,
235
+ EXTENSIONS_DIR,
236
+ EXTENSION_SCHEMA,
237
+ EXTENSION_SHARD,
223
238
  FLOW_SCHEMA,
224
239
  HANDOFF_SCHEMA,
240
+ MANIFEST_EXTENSION,
225
241
  PAD_AFTER_MAX,
226
242
  PAD_AFTER_MIN,
243
+ PROJECT_EXTENSION_STEM,
227
244
  PROJECT_LOCALE_SCENE,
228
245
  PROJECT_SCHEMA,
229
246
  RERECORD_STATUS,
@@ -233,6 +250,7 @@ var AUTHORING_SCHEMA = "patter/authoring@0";
233
250
  SAVE_VERSION,
234
251
  SPEAKER_PATTERN_KEY,
235
252
  STRINGS_SCHEMA,
253
+ UNRESOLVED_KEY,
236
254
  castStringKey,
237
255
  deriveRecordingFolders,
238
256
  effectiveGameId,
package/dist/index.d.cts CHANGED
@@ -33,6 +33,10 @@ interface LineBeat {
33
33
  * Never negative on a snippet's last line or text beat: the runtime clamps it to zero there. */
34
34
  padAfter?: number;
35
35
  gameData?: GameData;
36
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
37
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
38
+ * node has none. */
39
+ extensions?: NodeExtensions;
36
40
  /** Author-defined freeform tags (#215): a cross-cutting label layer that travels to the runtime.
37
41
  * At runtime a beat's tags are the UNION of its own and every ancestor's (scene → block → group(s) →
38
42
  * snippet → beat). Each tag is letters/digits/symbols with NO comma and NO whitespace; deduped. */
@@ -53,6 +57,10 @@ interface TextBeat {
53
57
  * Never negative on a snippet's last line or text beat: the runtime clamps it to zero there. */
54
58
  padAfter?: number;
55
59
  gameData?: GameData;
60
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
61
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
62
+ * node has none. */
63
+ extensions?: NodeExtensions;
56
64
  /** Author tags (#215). See LineBeat.tags. */
57
65
  tags?: string[];
58
66
  }
@@ -66,6 +74,10 @@ interface GameEventBeat {
66
74
  id: string;
67
75
  kind: "gameEvent";
68
76
  gameData?: GameData;
77
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
78
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
79
+ * node has none. */
80
+ extensions?: NodeExtensions;
69
81
  /** Author tags (#215). See LineBeat.tags. */
70
82
  tags?: string[];
71
83
  }
@@ -400,8 +412,10 @@ interface BundleQualifier {
400
412
  * the bundle; this type states the resulting contract for anyone reading a bundle. */
401
413
  type BundleCastMember = Omit<CastMember, "notes" | "actor" | "gender">;
402
414
  /** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).
403
- * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`. */
404
- type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent";
415
+ * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`.
416
+ * `option` is a choice's option: a group that is a direct child of a `choice` group, whose gameData the
417
+ * host reads off the option `getChoices` hands it. Any other group's gameData stays undeclared. */
418
+ type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent" | "option";
405
419
  /** The value type of a gameData field: the family's one field-type list, shared with Storylets' card and
406
420
  * outcome fields (patterkit design/family-parity-2026-10.md item 9, patter-qualities.md section 1). Drives
407
421
  * the inspector's editor widget (text / textarea / number / toggle / enum dropdown / flags checklist /
@@ -604,6 +618,7 @@ interface ProjectFile {
604
618
  flow?: string;
605
619
  strings?: string;
606
620
  authoring?: string;
621
+ extensions?: string;
607
622
  };
608
623
  /** `bundle`: the compiled `.patterc` output path (relative to the project root,
609
624
  * or absolute); default `../patter-dist/<project-file-stem>.patterc`, beside the project (spec §11).
@@ -648,6 +663,15 @@ interface ProjectFile {
648
663
  * Absent = `DEFAULT_PAD_AFTER`. */
649
664
  padAfterDefault?: number;
650
665
  gameDataFields?: GameDataFields;
666
+ /**
667
+ * The Patter extensions this project takes, by id (ruled 2026-10-18: the project opts in), each pinned to a
668
+ * version, with its settings (the host fields its claims present, by setting name). The manifest itself
669
+ * sits in `extensions/<id>.extension.json`, and the data in `.patterextension` shards. Authoring config:
670
+ * the bundle carries each one's compiled declarations, values, and resolved settings
671
+ * (`BundleExtension.settings`), never this block itself. An extension named here whose manifest is missing
672
+ * is a warning, and its data is left untouched.
673
+ */
674
+ extensions?: Record<string, ProjectExtension>;
651
675
  /** Ordered writing-status ladder (not-done -> done); default `DEFAULT_WRITING_STATUSES`. */
652
676
  writingStatuses?: WritingStatusDecl[];
653
677
  /** Ordered recording-status ladder (not-done -> done); default `DEFAULT_RECORDING_STATUSES`. */
@@ -951,6 +975,8 @@ interface CompiledSnippet {
951
975
  onExit?: CompiledEffect[];
952
976
  padAfterDefault?: number;
953
977
  gameData?: GameData;
978
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
979
+ extensions?: NodeExtensions;
954
980
  tags?: string[];
955
981
  jump?: Jump;
956
982
  secretUntilEligible?: boolean;
@@ -971,6 +997,8 @@ interface CompiledGroup {
971
997
  children: Array<CompiledGroup | CompiledSnippet>;
972
998
  padAfterDefault?: number;
973
999
  gameData?: GameData;
1000
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1001
+ extensions?: NodeExtensions;
974
1002
  tags?: string[];
975
1003
  /** Option-position fields (spec §5) - only when a direct child of a `choice`. */
976
1004
  prompt?: PromptBeat;
@@ -989,6 +1017,8 @@ interface CompiledBlock {
989
1017
  children: Array<CompiledGroup | CompiledSnippet>;
990
1018
  padAfterDefault?: number;
991
1019
  gameData?: GameData;
1020
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1021
+ extensions?: NodeExtensions;
992
1022
  tags?: string[];
993
1023
  }
994
1024
  interface CompiledScene {
@@ -999,6 +1029,8 @@ interface CompiledScene {
999
1029
  gameId?: string;
1000
1030
  padAfterDefault?: number;
1001
1031
  gameData?: GameData;
1032
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1033
+ extensions?: NodeExtensions;
1002
1034
  tags?: string[];
1003
1035
  onEntry?: CompiledEffect[];
1004
1036
  sceneProps?: PropertyDecl[];
@@ -1039,6 +1071,13 @@ interface Bundle {
1039
1071
  * one. Absent when none. */
1040
1072
  externalScopes?: string[];
1041
1073
  gameDataFields?: GameDataFields;
1074
+ /**
1075
+ * The extensions the project takes, by id: each one's version and its compiled declarations by node kind,
1076
+ * every default resolved (what `extensionDecls` reads), the project node's own values, and its settings,
1077
+ * resolved (what `extensionSettings` reads). A node's values sit on the node, under `extensions`. Absent
1078
+ * when the project takes none, so a bundle without them is byte for byte what it was.
1079
+ */
1080
+ extensions?: Record<string, BundleExtension>;
1042
1081
  scenes: Record<string, CompiledScene>;
1043
1082
  /** locale -> (beatId -> text). In "embedded" localisation this carries every included locale; in "ids"
1044
1083
  * it is EMPTY (the runtime emits beat IDs), unless `localisation.sourceDebug` embedded the source locale
@@ -1211,24 +1250,116 @@ declare const DEFAULT_LAYOUT: {
1211
1250
  readonly flow: "scenes/";
1212
1251
  readonly strings: "loc/";
1213
1252
  readonly authoring: "authoring/";
1253
+ readonly extensions: "extensions/";
1214
1254
  };
1215
- /** A project's layout with the defaults filled in: the one place they are written down (the loader,
1216
- * validate, localisation, the scene scaffold, and Patterpad each had a copy). */
1217
- declare function projectLayout(project: {
1218
- layout?: {
1219
- flow?: string;
1220
- strings?: string;
1221
- authoring?: string;
1222
- };
1223
- }): {
1255
+ /** Where a project keeps each kind of shard, relative to the project root (`ProjectFile.layout`). */
1256
+ interface ProjectLayout {
1224
1257
  flow: string;
1225
1258
  strings: string;
1226
1259
  authoring: string;
1227
- };
1260
+ extensions: string;
1261
+ }
1262
+ /** A project's layout with the defaults filled in: the one place they are written down (the loader,
1263
+ * validate, localisation, the scene scaffold, and Patterpad each had a copy). */
1264
+ declare function projectLayout(project: {
1265
+ layout?: Partial<ProjectLayout>;
1266
+ }): ProjectLayout;
1228
1267
  /** The source shards' schema tags (spec §10), written by every tool that makes one. */
1229
1268
  declare const FLOW_SCHEMA = "patter/flow@0";
1230
1269
  declare const PROJECT_SCHEMA = "patter/project@0";
1231
1270
  declare const STRINGS_SCHEMA = "patter/strings@0";
1232
1271
  declare const AUTHORING_SCHEMA = "patter/authoring@0";
1272
+ /** An extension data shard's schema. */
1273
+ declare const EXTENSION_SCHEMA = "patter/extension@0";
1274
+ /** An extension data shard's file suffix: one per extension per scene
1275
+ * (`<layout.extensions>/<extension id>/<scene file stem>.patterextension`), and one for project-level
1276
+ * data (`<layout.extensions>/<extension id>/_project.patterextension`). Its own shard because a tool's
1277
+ * pass and a writer's edit must not collide on one file. */
1278
+ declare const EXTENSION_SHARD = ".patterextension";
1279
+ /** The stem of an extension's project-level data shard, beside its scene shards. */
1280
+ declare const PROJECT_EXTENSION_STEM = "_project";
1281
+ /**
1282
+ * An extension's manifest, as a project keeps it: `extensions/<id>.extension.json` at the project root
1283
+ * (`@wildwinter/toolkit/extensions`' `MANIFEST_FILE_SUFFIX`). Not a shard: JSON the extension ships, never
1284
+ * written by the tools nor put in the canonical form, but source all the same, so a pack carries it and a
1285
+ * project compiles from it.
1286
+ */
1287
+ declare const MANIFEST_EXTENSION = ".extension.json";
1288
+ /** Where a project keeps its extensions' manifests, relative to the root. Fixed, unlike the data shards'
1289
+ * folder, so a merge driver handed one shard's path can always find the manifest. */
1290
+ declare const EXTENSIONS_DIR = "extensions";
1291
+ /**
1292
+ * The extensions a project takes (`ProjectFile.extensions`), one each: the version it is pinned to and its
1293
+ * settings (the host fields its claims present, by setting name).
1294
+ */
1295
+ interface ProjectExtension {
1296
+ version: string;
1297
+ settings?: Record<string, string>;
1298
+ }
1299
+ /**
1300
+ * One extension's data for one scene, or for the project (`scene: "@project"`).
1301
+ *
1302
+ * `entries` is the extension's, by node kind (`scene`, `block`, `snippet`, `line`, `text`, `gameEvent`,
1303
+ * `option`, `project`) and then node id, and the canonical form never reorders anything inside it: its
1304
+ * lists are the extension's, in the extension's order. The values are raw, as a tool wrote them; the
1305
+ * extension's manifest says their types.
1306
+ */
1307
+ interface ExtensionShard {
1308
+ schema: typeof EXTENSION_SCHEMA;
1309
+ /** The extension's id: the same as the folder the shard sits in. */
1310
+ extension: string;
1311
+ /** The scene this shard's entries belong to, or `PROJECT_LOCALE_SCENE` (`"@project"`) for the project's. */
1312
+ scene: string;
1313
+ entries: Record<string, Record<string, Record<string, unknown>>>;
1314
+ }
1315
+ /**
1316
+ * The reserved key a merged shard carries an unresolved merge under, in every shard type
1317
+ * (`@wildwinter/toolkit/extensions`' `UNRESOLVED_KEY`, ruled 2026-10-18 in both formats): the clashes, each
1318
+ * with its path, its kind and the three values, ours being what the shard now holds. Validation, export
1319
+ * and pack refuse a shard carrying it; resolving removes it.
1320
+ */
1321
+ declare const UNRESOLVED_KEY = "unresolved";
1322
+ /** One extension as the bundle carries it. */
1323
+ interface BundleExtension {
1324
+ version: string;
1325
+ /** Compiled declarations by node kind: no editor-only fields, no editor words. */
1326
+ fields: Record<string, ExtensionFieldDecl[]>;
1327
+ /** The project node's values, raw, when it has any. */
1328
+ project?: Record<string, unknown>;
1329
+ /**
1330
+ * Every setting the manifest declares, by setting name, keys sorted: the project's value (`ProjectExtension.
1331
+ * settings`), or else the setting's default (`@wildwinter/toolkit/extensions`' `settingValues`). A setting
1332
+ * names a host field, so a game reads here which gameData field holds what the extension claims. Present
1333
+ * only when the manifest declares settings.
1334
+ */
1335
+ settings?: Record<string, string>;
1336
+ }
1337
+ /**
1338
+ * A compiled extension field declaration (`@wildwinter/toolkit/extensions`' FieldDecl, less the editor's
1339
+ * words). Its `default` is always present. Structured types carry their parts: `fields` for a record,
1340
+ * `key` and `of` for a map, `of` and `id` for a list, `to` for a reference.
1341
+ */
1342
+ interface ExtensionFieldDecl {
1343
+ name: string;
1344
+ type: string;
1345
+ default: unknown;
1346
+ values?: string[];
1347
+ stages?: string[];
1348
+ to?: string;
1349
+ fields?: ExtensionFieldDecl[];
1350
+ key?: {
1351
+ type: string;
1352
+ to?: string;
1353
+ };
1354
+ of?: Omit<ExtensionFieldDecl, "name" | "default"> & {
1355
+ default?: unknown;
1356
+ };
1357
+ id?: string;
1358
+ min?: number;
1359
+ max?: number;
1360
+ required?: boolean;
1361
+ }
1362
+ /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1363
+ type NodeExtensions = Record<string, Record<string, unknown>>;
1233
1364
 
1234
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, 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, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, 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, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectFile, 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, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, speakerLabelKey, usedQualifiers, usedSpeakerPairs, walkNodes };
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 };
package/dist/index.d.ts CHANGED
@@ -33,6 +33,10 @@ interface LineBeat {
33
33
  * Never negative on a snippet's last line or text beat: the runtime clamps it to zero there. */
34
34
  padAfter?: number;
35
35
  gameData?: GameData;
36
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
37
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
38
+ * node has none. */
39
+ extensions?: NodeExtensions;
36
40
  /** Author-defined freeform tags (#215): a cross-cutting label layer that travels to the runtime.
37
41
  * At runtime a beat's tags are the UNION of its own and every ancestor's (scene → block → group(s) →
38
42
  * snippet → beat). Each tag is letters/digits/symbols with NO comma and NO whitespace; deduped. */
@@ -53,6 +57,10 @@ interface TextBeat {
53
57
  * Never negative on a snippet's last line or text beat: the runtime clamps it to zero there. */
54
58
  padAfter?: number;
55
59
  gameData?: GameData;
60
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
61
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
62
+ * node has none. */
63
+ extensions?: NodeExtensions;
56
64
  /** Author tags (#215). See LineBeat.tags. */
57
65
  tags?: string[];
58
66
  }
@@ -66,6 +74,10 @@ interface GameEventBeat {
66
74
  id: string;
67
75
  kind: "gameEvent";
68
76
  gameData?: GameData;
77
+ /** Bundle only: this node's extension values, by extension id, raw as authored (editor-only fields
78
+ * stripped). Source keeps them in extension shards (`.patterextension`), never here. Absent when the
79
+ * node has none. */
80
+ extensions?: NodeExtensions;
69
81
  /** Author tags (#215). See LineBeat.tags. */
70
82
  tags?: string[];
71
83
  }
@@ -400,8 +412,10 @@ interface BundleQualifier {
400
412
  * the bundle; this type states the resulting contract for anyone reading a bundle. */
401
413
  type BundleCastMember = Omit<CastMember, "notes" | "actor" | "gender">;
402
414
  /** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).
403
- * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`. */
404
- type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent";
415
+ * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`.
416
+ * `option` is a choice's option: a group that is a direct child of a `choice` group, whose gameData the
417
+ * host reads off the option `getChoices` hands it. Any other group's gameData stays undeclared. */
418
+ type GameDataNodeKind = "scene" | "block" | "snippet" | "line" | "text" | "gameEvent" | "option";
405
419
  /** The value type of a gameData field: the family's one field-type list, shared with Storylets' card and
406
420
  * outcome fields (patterkit design/family-parity-2026-10.md item 9, patter-qualities.md section 1). Drives
407
421
  * the inspector's editor widget (text / textarea / number / toggle / enum dropdown / flags checklist /
@@ -604,6 +618,7 @@ interface ProjectFile {
604
618
  flow?: string;
605
619
  strings?: string;
606
620
  authoring?: string;
621
+ extensions?: string;
607
622
  };
608
623
  /** `bundle`: the compiled `.patterc` output path (relative to the project root,
609
624
  * or absolute); default `../patter-dist/<project-file-stem>.patterc`, beside the project (spec §11).
@@ -648,6 +663,15 @@ interface ProjectFile {
648
663
  * Absent = `DEFAULT_PAD_AFTER`. */
649
664
  padAfterDefault?: number;
650
665
  gameDataFields?: GameDataFields;
666
+ /**
667
+ * The Patter extensions this project takes, by id (ruled 2026-10-18: the project opts in), each pinned to a
668
+ * version, with its settings (the host fields its claims present, by setting name). The manifest itself
669
+ * sits in `extensions/<id>.extension.json`, and the data in `.patterextension` shards. Authoring config:
670
+ * the bundle carries each one's compiled declarations, values, and resolved settings
671
+ * (`BundleExtension.settings`), never this block itself. An extension named here whose manifest is missing
672
+ * is a warning, and its data is left untouched.
673
+ */
674
+ extensions?: Record<string, ProjectExtension>;
651
675
  /** Ordered writing-status ladder (not-done -> done); default `DEFAULT_WRITING_STATUSES`. */
652
676
  writingStatuses?: WritingStatusDecl[];
653
677
  /** Ordered recording-status ladder (not-done -> done); default `DEFAULT_RECORDING_STATUSES`. */
@@ -951,6 +975,8 @@ interface CompiledSnippet {
951
975
  onExit?: CompiledEffect[];
952
976
  padAfterDefault?: number;
953
977
  gameData?: GameData;
978
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
979
+ extensions?: NodeExtensions;
954
980
  tags?: string[];
955
981
  jump?: Jump;
956
982
  secretUntilEligible?: boolean;
@@ -971,6 +997,8 @@ interface CompiledGroup {
971
997
  children: Array<CompiledGroup | CompiledSnippet>;
972
998
  padAfterDefault?: number;
973
999
  gameData?: GameData;
1000
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1001
+ extensions?: NodeExtensions;
974
1002
  tags?: string[];
975
1003
  /** Option-position fields (spec §5) - only when a direct child of a `choice`. */
976
1004
  prompt?: PromptBeat;
@@ -989,6 +1017,8 @@ interface CompiledBlock {
989
1017
  children: Array<CompiledGroup | CompiledSnippet>;
990
1018
  padAfterDefault?: number;
991
1019
  gameData?: GameData;
1020
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1021
+ extensions?: NodeExtensions;
992
1022
  tags?: string[];
993
1023
  }
994
1024
  interface CompiledScene {
@@ -999,6 +1029,8 @@ interface CompiledScene {
999
1029
  gameId?: string;
1000
1030
  padAfterDefault?: number;
1001
1031
  gameData?: GameData;
1032
+ /** This node's extension values, by extension id (editor-only fields stripped). Absent when it has none. */
1033
+ extensions?: NodeExtensions;
1002
1034
  tags?: string[];
1003
1035
  onEntry?: CompiledEffect[];
1004
1036
  sceneProps?: PropertyDecl[];
@@ -1039,6 +1071,13 @@ interface Bundle {
1039
1071
  * one. Absent when none. */
1040
1072
  externalScopes?: string[];
1041
1073
  gameDataFields?: GameDataFields;
1074
+ /**
1075
+ * The extensions the project takes, by id: each one's version and its compiled declarations by node kind,
1076
+ * every default resolved (what `extensionDecls` reads), the project node's own values, and its settings,
1077
+ * resolved (what `extensionSettings` reads). A node's values sit on the node, under `extensions`. Absent
1078
+ * when the project takes none, so a bundle without them is byte for byte what it was.
1079
+ */
1080
+ extensions?: Record<string, BundleExtension>;
1042
1081
  scenes: Record<string, CompiledScene>;
1043
1082
  /** locale -> (beatId -> text). In "embedded" localisation this carries every included locale; in "ids"
1044
1083
  * it is EMPTY (the runtime emits beat IDs), unless `localisation.sourceDebug` embedded the source locale
@@ -1211,24 +1250,116 @@ declare const DEFAULT_LAYOUT: {
1211
1250
  readonly flow: "scenes/";
1212
1251
  readonly strings: "loc/";
1213
1252
  readonly authoring: "authoring/";
1253
+ readonly extensions: "extensions/";
1214
1254
  };
1215
- /** A project's layout with the defaults filled in: the one place they are written down (the loader,
1216
- * validate, localisation, the scene scaffold, and Patterpad each had a copy). */
1217
- declare function projectLayout(project: {
1218
- layout?: {
1219
- flow?: string;
1220
- strings?: string;
1221
- authoring?: string;
1222
- };
1223
- }): {
1255
+ /** Where a project keeps each kind of shard, relative to the project root (`ProjectFile.layout`). */
1256
+ interface ProjectLayout {
1224
1257
  flow: string;
1225
1258
  strings: string;
1226
1259
  authoring: string;
1227
- };
1260
+ extensions: string;
1261
+ }
1262
+ /** A project's layout with the defaults filled in: the one place they are written down (the loader,
1263
+ * validate, localisation, the scene scaffold, and Patterpad each had a copy). */
1264
+ declare function projectLayout(project: {
1265
+ layout?: Partial<ProjectLayout>;
1266
+ }): ProjectLayout;
1228
1267
  /** The source shards' schema tags (spec §10), written by every tool that makes one. */
1229
1268
  declare const FLOW_SCHEMA = "patter/flow@0";
1230
1269
  declare const PROJECT_SCHEMA = "patter/project@0";
1231
1270
  declare const STRINGS_SCHEMA = "patter/strings@0";
1232
1271
  declare const AUTHORING_SCHEMA = "patter/authoring@0";
1272
+ /** An extension data shard's schema. */
1273
+ declare const EXTENSION_SCHEMA = "patter/extension@0";
1274
+ /** An extension data shard's file suffix: one per extension per scene
1275
+ * (`<layout.extensions>/<extension id>/<scene file stem>.patterextension`), and one for project-level
1276
+ * data (`<layout.extensions>/<extension id>/_project.patterextension`). Its own shard because a tool's
1277
+ * pass and a writer's edit must not collide on one file. */
1278
+ declare const EXTENSION_SHARD = ".patterextension";
1279
+ /** The stem of an extension's project-level data shard, beside its scene shards. */
1280
+ declare const PROJECT_EXTENSION_STEM = "_project";
1281
+ /**
1282
+ * An extension's manifest, as a project keeps it: `extensions/<id>.extension.json` at the project root
1283
+ * (`@wildwinter/toolkit/extensions`' `MANIFEST_FILE_SUFFIX`). Not a shard: JSON the extension ships, never
1284
+ * written by the tools nor put in the canonical form, but source all the same, so a pack carries it and a
1285
+ * project compiles from it.
1286
+ */
1287
+ declare const MANIFEST_EXTENSION = ".extension.json";
1288
+ /** Where a project keeps its extensions' manifests, relative to the root. Fixed, unlike the data shards'
1289
+ * folder, so a merge driver handed one shard's path can always find the manifest. */
1290
+ declare const EXTENSIONS_DIR = "extensions";
1291
+ /**
1292
+ * The extensions a project takes (`ProjectFile.extensions`), one each: the version it is pinned to and its
1293
+ * settings (the host fields its claims present, by setting name).
1294
+ */
1295
+ interface ProjectExtension {
1296
+ version: string;
1297
+ settings?: Record<string, string>;
1298
+ }
1299
+ /**
1300
+ * One extension's data for one scene, or for the project (`scene: "@project"`).
1301
+ *
1302
+ * `entries` is the extension's, by node kind (`scene`, `block`, `snippet`, `line`, `text`, `gameEvent`,
1303
+ * `option`, `project`) and then node id, and the canonical form never reorders anything inside it: its
1304
+ * lists are the extension's, in the extension's order. The values are raw, as a tool wrote them; the
1305
+ * extension's manifest says their types.
1306
+ */
1307
+ interface ExtensionShard {
1308
+ schema: typeof EXTENSION_SCHEMA;
1309
+ /** The extension's id: the same as the folder the shard sits in. */
1310
+ extension: string;
1311
+ /** The scene this shard's entries belong to, or `PROJECT_LOCALE_SCENE` (`"@project"`) for the project's. */
1312
+ scene: string;
1313
+ entries: Record<string, Record<string, Record<string, unknown>>>;
1314
+ }
1315
+ /**
1316
+ * The reserved key a merged shard carries an unresolved merge under, in every shard type
1317
+ * (`@wildwinter/toolkit/extensions`' `UNRESOLVED_KEY`, ruled 2026-10-18 in both formats): the clashes, each
1318
+ * with its path, its kind and the three values, ours being what the shard now holds. Validation, export
1319
+ * and pack refuse a shard carrying it; resolving removes it.
1320
+ */
1321
+ declare const UNRESOLVED_KEY = "unresolved";
1322
+ /** One extension as the bundle carries it. */
1323
+ interface BundleExtension {
1324
+ version: string;
1325
+ /** Compiled declarations by node kind: no editor-only fields, no editor words. */
1326
+ fields: Record<string, ExtensionFieldDecl[]>;
1327
+ /** The project node's values, raw, when it has any. */
1328
+ project?: Record<string, unknown>;
1329
+ /**
1330
+ * Every setting the manifest declares, by setting name, keys sorted: the project's value (`ProjectExtension.
1331
+ * settings`), or else the setting's default (`@wildwinter/toolkit/extensions`' `settingValues`). A setting
1332
+ * names a host field, so a game reads here which gameData field holds what the extension claims. Present
1333
+ * only when the manifest declares settings.
1334
+ */
1335
+ settings?: Record<string, string>;
1336
+ }
1337
+ /**
1338
+ * A compiled extension field declaration (`@wildwinter/toolkit/extensions`' FieldDecl, less the editor's
1339
+ * words). Its `default` is always present. Structured types carry their parts: `fields` for a record,
1340
+ * `key` and `of` for a map, `of` and `id` for a list, `to` for a reference.
1341
+ */
1342
+ interface ExtensionFieldDecl {
1343
+ name: string;
1344
+ type: string;
1345
+ default: unknown;
1346
+ values?: string[];
1347
+ stages?: string[];
1348
+ to?: string;
1349
+ fields?: ExtensionFieldDecl[];
1350
+ key?: {
1351
+ type: string;
1352
+ to?: string;
1353
+ };
1354
+ of?: Omit<ExtensionFieldDecl, "name" | "default"> & {
1355
+ default?: unknown;
1356
+ };
1357
+ id?: string;
1358
+ min?: number;
1359
+ max?: number;
1360
+ required?: boolean;
1361
+ }
1362
+ /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1363
+ type NodeExtensions = Record<string, Record<string, unknown>>;
1233
1364
 
1234
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, 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, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, 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, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectFile, 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, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, formatSpeakerLabel, gameIdify, isContentlessBeat, isValidGameId, projectLayout, projectQualifiers, qualifierStringKey, speakerLabelKey, usedQualifiers, usedSpeakerPairs, walkNodes };
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 };
package/dist/index.js CHANGED
@@ -140,7 +140,7 @@ var DEFAULT_CAPTION_DELIMITERS = { open: "[", close: "]" };
140
140
  var DEFAULT_CAPTION_CHARACTER = "SFX";
141
141
  var SAVE_SCHEMA = "patter/save@0";
142
142
  var SAVE_VERSION = 3;
143
- var DEFAULT_LAYOUT = { flow: "scenes/", strings: "loc/", authoring: "authoring/" };
143
+ var DEFAULT_LAYOUT = { flow: "scenes/", strings: "loc/", authoring: "authoring/", extensions: "extensions/" };
144
144
  function projectLayout(project) {
145
145
  return { ...DEFAULT_LAYOUT, ...project.layout };
146
146
  }
@@ -148,6 +148,12 @@ var FLOW_SCHEMA = "patter/flow@0";
148
148
  var PROJECT_SCHEMA = "patter/project@0";
149
149
  var STRINGS_SCHEMA = "patter/strings@0";
150
150
  var AUTHORING_SCHEMA = "patter/authoring@0";
151
+ var EXTENSION_SCHEMA = "patter/extension@0";
152
+ var EXTENSION_SHARD = ".patterextension";
153
+ var PROJECT_EXTENSION_STEM = "_project";
154
+ var MANIFEST_EXTENSION = ".extension.json";
155
+ var EXTENSIONS_DIR = "extensions";
156
+ var UNRESOLVED_KEY = "unresolved";
151
157
  export {
152
158
  AUTHORING_SCHEMA,
153
159
  COMMON_GENDERS,
@@ -160,10 +166,15 @@ export {
160
166
  DEFAULT_RECORDING_STATUSES,
161
167
  DEFAULT_SPEAKER_PATTERN,
162
168
  DEFAULT_WRITING_STATUSES,
169
+ EXTENSIONS_DIR,
170
+ EXTENSION_SCHEMA,
171
+ EXTENSION_SHARD,
163
172
  FLOW_SCHEMA,
164
173
  HANDOFF_SCHEMA,
174
+ MANIFEST_EXTENSION,
165
175
  PAD_AFTER_MAX,
166
176
  PAD_AFTER_MIN,
177
+ PROJECT_EXTENSION_STEM,
167
178
  PROJECT_LOCALE_SCENE,
168
179
  PROJECT_SCHEMA,
169
180
  RERECORD_STATUS,
@@ -173,6 +184,7 @@ export {
173
184
  SAVE_VERSION,
174
185
  SPEAKER_PATTERN_KEY,
175
186
  STRINGS_SCHEMA,
187
+ UNRESOLVED_KEY,
176
188
  castStringKey,
177
189
  deriveRecordingFolders,
178
190
  effectiveGameId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/model",
3
- "version": "0.15.0",
3
+ "version": "0.17.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",