@patterkit/runtime 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
2
  import { ScopeResolver, ScopeRegistry, ScopeDeclaration } from '@wildwinter/scoperegistry';
3
- import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, GameDataField, GameDataNodeKind } from '@patterkit/model';
3
+ import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
4
  export { Bundle } from '@patterkit/model';
5
5
 
6
6
  type SelectableNode = CompiledGroup | CompiledSnippet;
@@ -705,6 +705,143 @@ declare class Flow {
705
705
  private seedScene;
706
706
  }
707
707
 
708
+ /** Which bundle this is: identity, staleness fingerprints, and how it ships. */
709
+ interface BundleIdentity {
710
+ /** The bundle schema tag ("patter/bundle@0"). */
711
+ schema: string;
712
+ /** The project name. A save must agree with this. */
713
+ project: string;
714
+ /** The authored bundle version, if the project stamps one. */
715
+ version?: string;
716
+ /** Fingerprint over the WHOLE bundle: what binds saves and gates staleness. */
717
+ hash?: string;
718
+ /** The same fingerprint with the string tables left out. Equal structureHash
719
+ * plus a different hash means a TEXT-ONLY edit, which is what makes a live
720
+ * hot-swap safe. Showing both lets an integrator tell those apart at sight. */
721
+ structureHash?: string;
722
+ /** Project-wide VO mode. */
723
+ voiced: boolean;
724
+ defaultLocale: string;
725
+ locales: string[];
726
+ /** How strings ship: "embedded" (the runtime resolves text) or "ids" (the
727
+ * runtime emits beat IDs and the game localises them itself). */
728
+ localisation: "embedded" | "ids";
729
+ /** True when the source locale was embedded purely for debug playback. Such a
730
+ * build is NOT shippable, which is worth saying loudly in an inspector. */
731
+ sourceDebug: boolean;
732
+ }
733
+ /** One scene, and the addresses game code may aim at inside it. */
734
+ interface AddressSummary {
735
+ /** The host-facing scene address: what `runFlow` / `goto` take. Derived from
736
+ * the name when the author set no explicit gameId, exactly as the runtime
737
+ * resolves it, so this list is the truth rather than an approximation. */
738
+ gameId: string;
739
+ /** The authored scene name, for recognising the row. */
740
+ name: string;
741
+ /** Block addresses within this scene. A block address is SCENE-SCOPED: the
742
+ * pair is the address, which is why these are nested rather than flattened. */
743
+ blocks: {
744
+ gameId: string;
745
+ name: string;
746
+ }[];
747
+ }
748
+ /** One author-defined gameData field: part of the host-facing data surface. */
749
+ interface GameDataFieldSummary {
750
+ name: string;
751
+ type: string;
752
+ /** Whether the schema carries a fallback. Sparse storage means a node that
753
+ * sets nothing reads this, so a field with no default can arrive absent. */
754
+ hasDefault: boolean;
755
+ /** Allowed values for an enum field: the set host code switches on. */
756
+ values?: string[];
757
+ purpose?: string;
758
+ }
759
+ /** The gameData fields declared for one kind of node. */
760
+ interface GameDataSummary {
761
+ kind: GameDataNodeKind;
762
+ fields: GameDataFieldSummary[];
763
+ }
764
+ /** One declared property. `hasDefault` rather than the value itself: an
765
+ * inspector wants to know whether the host MUST supply something. */
766
+ interface PropertySummary {
767
+ name: string;
768
+ type: PropertyType;
769
+ hasDefault: boolean;
770
+ default?: ScalarValue$1;
771
+ /** Shared across all flows, or kept per-flow. Defaults differ by scope
772
+ * (`@patter` shared, `@scene` per-flow), so it is resolved here. */
773
+ shared: boolean;
774
+ }
775
+ /** A host scope (`@world` and friends): what the GAME must supply.
776
+ *
777
+ * The highest-value section of the whole description. Today an integrator
778
+ * discovers a missing world property when a condition silently reads a
779
+ * self-backed default and a branch never fires. */
780
+ interface HostScopeSummary {
781
+ /** The token after `@`, e.g. "world". */
782
+ token: string;
783
+ /** Scope-level read/write default for its declarations. */
784
+ writable: boolean;
785
+ /** An OPAQUE scope declares no names: any name is accepted, unchecked. The
786
+ * host contract is then "anything", which is worth showing as such rather
787
+ * than as an empty property list. */
788
+ opaque: boolean;
789
+ properties: PropertySummary[];
790
+ }
791
+ /** Story-owned declarations, for orientation rather than for calling. */
792
+ interface OwnedProperties {
793
+ /** Project-level (`@patter`). */
794
+ patter: PropertySummary[];
795
+ /** Per scene (`@scene`), keyed by the scene's host address. */
796
+ scene: {
797
+ gameId: string;
798
+ properties: PropertySummary[];
799
+ }[];
800
+ }
801
+ /** "Is this the right build?" at a glance. */
802
+ interface BundleCounts {
803
+ scenes: number;
804
+ blocks: number;
805
+ groups: number;
806
+ snippets: number;
807
+ /** Snippet beats. This is the SAME population `Engine.getBeatSequence` walks, deliberately, so a
808
+ * tool that lists beats and an inspector that counts them never disagree. Choice prompts are not
809
+ * in it - see `prompts`. */
810
+ beats: number;
811
+ /** Choice-option prompts: beats that live on a group rather than in a snippet.
812
+ *
813
+ * Counted separately rather than folded into `beats` because folding them in would make this
814
+ * number disagree with `getBeatSequence`, and leaving them out entirely would understate a
815
+ * choice-heavy story - a branching script could report a handful of beats and look like the wrong
816
+ * build. Neither silence nor a redefinition; a second row. */
817
+ prompts: number;
818
+ /** Beats that fire a game event rather than producing player-facing words. */
819
+ gameEvents: number;
820
+ /** Cast members the bundle carries (player-facing only; the compiler strips
821
+ * the authoring fields). */
822
+ cast: number;
823
+ }
824
+ interface BundleDescription {
825
+ identity: BundleIdentity;
826
+ /** Everything game code may aim at, in bundle order. */
827
+ addresses: AddressSummary[];
828
+ /** What the host must supply. */
829
+ hostScopes: HostScopeSummary[];
830
+ /** What the story owns. */
831
+ properties: OwnedProperties;
832
+ /** The author-defined data surface, grouped by node kind. */
833
+ gameData: GameDataSummary[];
834
+ counts: BundleCounts;
835
+ }
836
+ /**
837
+ * Describe a compiled bundle: what it is, and what a game may call on it.
838
+ *
839
+ * Pure and allocation-light. Safe to call from an editor inspector on every
840
+ * selection, though a details panel should still build its rows once rather
841
+ * than per repaint.
842
+ */
843
+ declare function describeBundle(bundle: Bundle): BundleDescription;
844
+
708
845
  /** The author-defined gameData fields declared for a node TYPE in a bundle (empty when none). */
709
846
  declare function gameDataFields(bundle: Bundle, kind: GameDataNodeKind): GameDataField[];
710
847
  /** One node's effective value for a field: its sparse OVERRIDE if present, else the field's declared
@@ -722,4 +859,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
722
859
  */
723
860
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
724
861
 
725
- 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 };
862
+ export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineOptions, type EngineSave, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertyRow, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
2
  import { ScopeResolver, ScopeRegistry, ScopeDeclaration } from '@wildwinter/scoperegistry';
3
- import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, GameDataField, GameDataNodeKind } from '@patterkit/model';
3
+ import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
4
  export { Bundle } from '@patterkit/model';
5
5
 
6
6
  type SelectableNode = CompiledGroup | CompiledSnippet;
@@ -705,6 +705,143 @@ declare class Flow {
705
705
  private seedScene;
706
706
  }
707
707
 
708
+ /** Which bundle this is: identity, staleness fingerprints, and how it ships. */
709
+ interface BundleIdentity {
710
+ /** The bundle schema tag ("patter/bundle@0"). */
711
+ schema: string;
712
+ /** The project name. A save must agree with this. */
713
+ project: string;
714
+ /** The authored bundle version, if the project stamps one. */
715
+ version?: string;
716
+ /** Fingerprint over the WHOLE bundle: what binds saves and gates staleness. */
717
+ hash?: string;
718
+ /** The same fingerprint with the string tables left out. Equal structureHash
719
+ * plus a different hash means a TEXT-ONLY edit, which is what makes a live
720
+ * hot-swap safe. Showing both lets an integrator tell those apart at sight. */
721
+ structureHash?: string;
722
+ /** Project-wide VO mode. */
723
+ voiced: boolean;
724
+ defaultLocale: string;
725
+ locales: string[];
726
+ /** How strings ship: "embedded" (the runtime resolves text) or "ids" (the
727
+ * runtime emits beat IDs and the game localises them itself). */
728
+ localisation: "embedded" | "ids";
729
+ /** True when the source locale was embedded purely for debug playback. Such a
730
+ * build is NOT shippable, which is worth saying loudly in an inspector. */
731
+ sourceDebug: boolean;
732
+ }
733
+ /** One scene, and the addresses game code may aim at inside it. */
734
+ interface AddressSummary {
735
+ /** The host-facing scene address: what `runFlow` / `goto` take. Derived from
736
+ * the name when the author set no explicit gameId, exactly as the runtime
737
+ * resolves it, so this list is the truth rather than an approximation. */
738
+ gameId: string;
739
+ /** The authored scene name, for recognising the row. */
740
+ name: string;
741
+ /** Block addresses within this scene. A block address is SCENE-SCOPED: the
742
+ * pair is the address, which is why these are nested rather than flattened. */
743
+ blocks: {
744
+ gameId: string;
745
+ name: string;
746
+ }[];
747
+ }
748
+ /** One author-defined gameData field: part of the host-facing data surface. */
749
+ interface GameDataFieldSummary {
750
+ name: string;
751
+ type: string;
752
+ /** Whether the schema carries a fallback. Sparse storage means a node that
753
+ * sets nothing reads this, so a field with no default can arrive absent. */
754
+ hasDefault: boolean;
755
+ /** Allowed values for an enum field: the set host code switches on. */
756
+ values?: string[];
757
+ purpose?: string;
758
+ }
759
+ /** The gameData fields declared for one kind of node. */
760
+ interface GameDataSummary {
761
+ kind: GameDataNodeKind;
762
+ fields: GameDataFieldSummary[];
763
+ }
764
+ /** One declared property. `hasDefault` rather than the value itself: an
765
+ * inspector wants to know whether the host MUST supply something. */
766
+ interface PropertySummary {
767
+ name: string;
768
+ type: PropertyType;
769
+ hasDefault: boolean;
770
+ default?: ScalarValue$1;
771
+ /** Shared across all flows, or kept per-flow. Defaults differ by scope
772
+ * (`@patter` shared, `@scene` per-flow), so it is resolved here. */
773
+ shared: boolean;
774
+ }
775
+ /** A host scope (`@world` and friends): what the GAME must supply.
776
+ *
777
+ * The highest-value section of the whole description. Today an integrator
778
+ * discovers a missing world property when a condition silently reads a
779
+ * self-backed default and a branch never fires. */
780
+ interface HostScopeSummary {
781
+ /** The token after `@`, e.g. "world". */
782
+ token: string;
783
+ /** Scope-level read/write default for its declarations. */
784
+ writable: boolean;
785
+ /** An OPAQUE scope declares no names: any name is accepted, unchecked. The
786
+ * host contract is then "anything", which is worth showing as such rather
787
+ * than as an empty property list. */
788
+ opaque: boolean;
789
+ properties: PropertySummary[];
790
+ }
791
+ /** Story-owned declarations, for orientation rather than for calling. */
792
+ interface OwnedProperties {
793
+ /** Project-level (`@patter`). */
794
+ patter: PropertySummary[];
795
+ /** Per scene (`@scene`), keyed by the scene's host address. */
796
+ scene: {
797
+ gameId: string;
798
+ properties: PropertySummary[];
799
+ }[];
800
+ }
801
+ /** "Is this the right build?" at a glance. */
802
+ interface BundleCounts {
803
+ scenes: number;
804
+ blocks: number;
805
+ groups: number;
806
+ snippets: number;
807
+ /** Snippet beats. This is the SAME population `Engine.getBeatSequence` walks, deliberately, so a
808
+ * tool that lists beats and an inspector that counts them never disagree. Choice prompts are not
809
+ * in it - see `prompts`. */
810
+ beats: number;
811
+ /** Choice-option prompts: beats that live on a group rather than in a snippet.
812
+ *
813
+ * Counted separately rather than folded into `beats` because folding them in would make this
814
+ * number disagree with `getBeatSequence`, and leaving them out entirely would understate a
815
+ * choice-heavy story - a branching script could report a handful of beats and look like the wrong
816
+ * build. Neither silence nor a redefinition; a second row. */
817
+ prompts: number;
818
+ /** Beats that fire a game event rather than producing player-facing words. */
819
+ gameEvents: number;
820
+ /** Cast members the bundle carries (player-facing only; the compiler strips
821
+ * the authoring fields). */
822
+ cast: number;
823
+ }
824
+ interface BundleDescription {
825
+ identity: BundleIdentity;
826
+ /** Everything game code may aim at, in bundle order. */
827
+ addresses: AddressSummary[];
828
+ /** What the host must supply. */
829
+ hostScopes: HostScopeSummary[];
830
+ /** What the story owns. */
831
+ properties: OwnedProperties;
832
+ /** The author-defined data surface, grouped by node kind. */
833
+ gameData: GameDataSummary[];
834
+ counts: BundleCounts;
835
+ }
836
+ /**
837
+ * Describe a compiled bundle: what it is, and what a game may call on it.
838
+ *
839
+ * Pure and allocation-light. Safe to call from an editor inspector on every
840
+ * selection, though a details panel should still build its rows once rather
841
+ * than per repaint.
842
+ */
843
+ declare function describeBundle(bundle: Bundle): BundleDescription;
844
+
708
845
  /** The author-defined gameData fields declared for a node TYPE in a bundle (empty when none). */
709
846
  declare function gameDataFields(bundle: Bundle, kind: GameDataNodeKind): GameDataField[];
710
847
  /** One node's effective value for a field: its sparse OVERRIDE if present, else the field's declared
@@ -722,4 +859,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
722
859
  */
723
860
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
724
861
 
725
- 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 };
862
+ export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineOptions, type EngineSave, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertyRow, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
package/dist/index.js CHANGED
@@ -1380,12 +1380,13 @@ function hostScopeDefault(decl) {
1380
1380
  }
1381
1381
  }
1382
1382
  function selfBackedResolver(decls) {
1383
+ const key = (name) => name.toLowerCase();
1383
1384
  const bag = /* @__PURE__ */ new Map();
1384
- for (const d of decls) bag.set(d.name, hostScopeDefault(d));
1385
+ for (const d of decls) bag.set(key(d.name), hostScopeDefault(d));
1385
1386
  return {
1386
- get: (name) => bag.get(name),
1387
+ get: (name) => bag.get(key(name)),
1387
1388
  set: (name, value) => {
1388
- bag.set(name, value);
1389
+ bag.set(key(name), value);
1389
1390
  }
1390
1391
  };
1391
1392
  }
@@ -1411,6 +1412,109 @@ function truthy(v) {
1411
1412
  return v.length > 0;
1412
1413
  }
1413
1414
 
1415
+ // src/describe.ts
1416
+ import { effectiveGameId as effectiveGameId2 } from "@patterkit/model";
1417
+ var isShared = (d, scopeDefault) => d.shared ?? scopeDefault;
1418
+ function summariseProperty(d, scopeDefault) {
1419
+ return {
1420
+ name: d.name,
1421
+ type: d.type,
1422
+ hasDefault: d.default !== void 0,
1423
+ ...d.default !== void 0 ? { default: d.default } : {},
1424
+ shared: isShared(d, scopeDefault)
1425
+ };
1426
+ }
1427
+ function summariseField(f) {
1428
+ return {
1429
+ name: f.name,
1430
+ type: f.type,
1431
+ hasDefault: f.default !== void 0,
1432
+ ...f.values ? { values: [...f.values] } : {},
1433
+ ...f.purpose ? { purpose: f.purpose } : {}
1434
+ };
1435
+ }
1436
+ function countBlock(block, counts) {
1437
+ counts.blocks++;
1438
+ const stack = [...block.children];
1439
+ while (stack.length) {
1440
+ const node = stack.pop();
1441
+ if (node.type === "group") {
1442
+ counts.groups++;
1443
+ if (node.prompt) counts.prompts++;
1444
+ stack.push(...node.children);
1445
+ continue;
1446
+ }
1447
+ counts.snippets++;
1448
+ for (const beat of node.beats ?? []) {
1449
+ counts.beats++;
1450
+ if (beat.kind === "gameEvent") counts.gameEvents++;
1451
+ }
1452
+ }
1453
+ }
1454
+ function describeBundle(bundle) {
1455
+ const counts = {
1456
+ scenes: 0,
1457
+ blocks: 0,
1458
+ groups: 0,
1459
+ snippets: 0,
1460
+ beats: 0,
1461
+ prompts: 0,
1462
+ gameEvents: 0,
1463
+ cast: bundle.cast?.length ?? 0
1464
+ };
1465
+ const addresses = [];
1466
+ const sceneProps = [];
1467
+ for (const scene of Object.values(bundle.scenes)) {
1468
+ counts.scenes++;
1469
+ const gameId = effectiveGameId2(scene);
1470
+ addresses.push({
1471
+ gameId,
1472
+ name: scene.name,
1473
+ blocks: scene.blocks.map((b) => ({ gameId: effectiveGameId2(b), name: b.name }))
1474
+ });
1475
+ for (const block of scene.blocks) countBlock(block, counts);
1476
+ if (scene.sceneProps?.length) {
1477
+ sceneProps.push({ gameId, properties: scene.sceneProps.map((d) => summariseProperty(d, false)) });
1478
+ }
1479
+ }
1480
+ const hostScopes = (bundle.scopeRegistry?.scopes ?? []).map((s) => ({
1481
+ token: s.token,
1482
+ writable: s.writable ?? true,
1483
+ opaque: s.declarations === void 0,
1484
+ // A host scope's values live outside the story, so "shared" is not a choice
1485
+ // its declarations make; they are world-wide by nature.
1486
+ properties: (s.declarations ?? []).map((d) => summariseProperty(d, true))
1487
+ }));
1488
+ const gameData = Object.entries(bundle.gameDataFields ?? {}).filter(([, fields]) => (fields?.length ?? 0) > 0).map(([kind, fields]) => ({
1489
+ kind,
1490
+ fields: (fields ?? []).map(summariseField)
1491
+ }));
1492
+ return {
1493
+ identity: {
1494
+ schema: bundle.schema,
1495
+ project: bundle.content.project,
1496
+ ...bundle.content.version !== void 0 ? { version: bundle.content.version } : {},
1497
+ ...bundle.content.hash !== void 0 ? { hash: bundle.content.hash } : {},
1498
+ ...bundle.content.structureHash !== void 0 ? { structureHash: bundle.content.structureHash } : {},
1499
+ voiced: bundle.voiced,
1500
+ defaultLocale: bundle.locales.default,
1501
+ locales: [...bundle.locales.included],
1502
+ // Absent means "embedded": the back-compat default a bundle written before
1503
+ // the field existed relies on.
1504
+ localisation: bundle.localisation?.mode ?? "embedded",
1505
+ sourceDebug: bundle.localisation?.sourceDebug ?? false
1506
+ },
1507
+ addresses,
1508
+ hostScopes,
1509
+ properties: {
1510
+ patter: (bundle.properties ?? []).map((d) => summariseProperty(d, true)),
1511
+ scene: sceneProps
1512
+ },
1513
+ gameData,
1514
+ counts
1515
+ };
1516
+ }
1517
+
1414
1518
  // src/gamedata.ts
1415
1519
  function gameDataFields(bundle, kind) {
1416
1520
  return bundle.gameDataFields?.[kind] ?? [];
@@ -1432,6 +1536,7 @@ export {
1432
1536
  Engine,
1433
1537
  Flow,
1434
1538
  buildTagIndex,
1539
+ describeBundle,
1435
1540
  effectiveGameData,
1436
1541
  gameDataFields,
1437
1542
  gameDataValue