@patterkit/runtime 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [614eaa8]
8
+ - @patterkit/model@0.3.0
9
+ - @patterkit/dialect@0.1.5
10
+
3
11
  ## 0.2.1
4
12
 
5
13
  ### Patch Changes
@@ -17,9 +25,31 @@ version number always means the same runtime behaviour. This package is versione
17
25
 
18
26
  ## [Unreleased]
19
27
 
28
+ ## [0.4.2] - 2026-08-19
29
+
30
+ ### Changed
31
+
32
+ - Version bump only, to keep the four Patterplay runtimes in lockstep. This release brings Unity,
33
+ Unreal and Godot up to the JavaScript runtime's declared-host-scope behaviour; the JavaScript
34
+ runtime itself is unchanged since 0.4.1.
35
+
36
+ ## [0.4.1] - 2026-08-19
37
+
38
+ ### Fixed
39
+
40
+ - **A declared host-scope property whose name carried a capital letter could never be read.** With no
41
+ host resolver bound for a scope, `@world` is self-backed from its declaration defaults - and that bag
42
+ was seeded with the declared name VERBATIM, while the compiler folds every property reference to
43
+ lower case. So a bundle declaring `@world.isNight` compiled a reference to `isnight`, the bag held
44
+ `isNight`, and the two never met: the read returned `undefined` and the gate took the falsy branch,
45
+ silently playing a different story from the same bundle. Only all-lowercase names ever worked. The
46
+ bag is now keyed lower case on seed, get and set. (`@patter` and `@scene` already normalised; this
47
+ resolver was the one that did not.) Declaring such a name is now refused at compile time as well.
48
+
20
49
  ## [0.4.0] - 2026-07-30
21
50
 
22
51
  ### Added
52
+
23
53
  - **State logger** (parity: previously JS-only). Watches the mutable runtime state - `@patter`
24
54
  globals, per-scene `@scene` props, and visit counts (shared + per-flow) - and reports what changed
25
55
  between captures, plus a per-step trace including `gameData`. Built on the engine's save-game, so
@@ -27,18 +57,21 @@ version number always means the same runtime behaviour. This package is versione
27
57
  every runtime.
28
58
 
29
59
  ### Fixed
60
+
30
61
  - The download now includes the **MIT `LICENSE` file**; previously the zip shipped with no licence
31
62
  text at all.
32
63
 
33
64
  ## [0.3.1] - 2026-07-22
34
65
 
35
66
  ### Changed
67
+
36
68
  - Version bump only, to keep the four Patterplay runtimes in lockstep. This release fixes
37
69
  Unreal-only build issues (see the Unreal changelog and #25); the JavaScript runtime is unchanged.
38
70
 
39
71
  ## [0.3.0] - 2026-07-21
40
72
 
41
73
  ### Added
74
+
42
75
  - **Host navigation.** `flow.goto(scene, block?)` sends a running flow to a Game ID address, behaving
43
76
  exactly like an authored `go` jump: the target scene's `onEntry` runs, arriving counts as a visit, and
44
77
  the callstack is replaced (pending call-returns discarded). Being a host action it lands immediately -
@@ -54,6 +87,7 @@ version number always means the same runtime behaviour. This package is versione
54
87
  (they were JS-only).
55
88
 
56
89
  ### Changed
90
+
57
91
  - Dropping a flow now FINISHES it. `closeFlow`, `engine.reset()` and re-opening a name all leave the old
58
92
  `Flow` inert (`advance()` reports the end, `goto()` refuses), so a stale reference a game still holds
59
93
  can no longer keep running scene entry effects and moving shared state. Re-opening a name still
@@ -62,6 +96,7 @@ version number always means the same runtime behaviour. This package is versione
62
96
  ## [0.2.2] - 2026-07-13
63
97
 
64
98
  ### Changed
99
+
65
100
  - Internal: the Best match (`specificity`) selection metric now uses the shared
66
101
  `@wildwinter/expr-specificity` package instead of a per-engine inline copy. Behaviour is
67
102
  unchanged and conformance-verified across all four engines.
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  Engine: () => Engine,
24
24
  Flow: () => Flow,
25
25
  buildTagIndex: () => buildTagIndex,
26
+ describeBundle: () => describeBundle,
26
27
  effectiveGameData: () => effectiveGameData,
27
28
  gameDataFields: () => gameDataFields,
28
29
  gameDataValue: () => gameDataValue
@@ -1411,12 +1412,13 @@ function hostScopeDefault(decl) {
1411
1412
  }
1412
1413
  }
1413
1414
  function selfBackedResolver(decls) {
1415
+ const key = (name) => name.toLowerCase();
1414
1416
  const bag = /* @__PURE__ */ new Map();
1415
- for (const d of decls) bag.set(d.name, hostScopeDefault(d));
1417
+ for (const d of decls) bag.set(key(d.name), hostScopeDefault(d));
1416
1418
  return {
1417
- get: (name) => bag.get(name),
1419
+ get: (name) => bag.get(key(name)),
1418
1420
  set: (name, value) => {
1419
- bag.set(name, value);
1421
+ bag.set(key(name), value);
1420
1422
  }
1421
1423
  };
1422
1424
  }
@@ -1442,6 +1444,109 @@ function truthy(v) {
1442
1444
  return v.length > 0;
1443
1445
  }
1444
1446
 
1447
+ // src/describe.ts
1448
+ var import_model2 = require("@patterkit/model");
1449
+ var isShared = (d, scopeDefault) => d.shared ?? scopeDefault;
1450
+ function summariseProperty(d, scopeDefault) {
1451
+ return {
1452
+ name: d.name,
1453
+ type: d.type,
1454
+ hasDefault: d.default !== void 0,
1455
+ ...d.default !== void 0 ? { default: d.default } : {},
1456
+ shared: isShared(d, scopeDefault)
1457
+ };
1458
+ }
1459
+ function summariseField(f) {
1460
+ return {
1461
+ name: f.name,
1462
+ type: f.type,
1463
+ hasDefault: f.default !== void 0,
1464
+ ...f.values ? { values: [...f.values] } : {},
1465
+ ...f.purpose ? { purpose: f.purpose } : {}
1466
+ };
1467
+ }
1468
+ function countBlock(block, counts) {
1469
+ counts.blocks++;
1470
+ const stack = [...block.children];
1471
+ while (stack.length) {
1472
+ const node = stack.pop();
1473
+ if (node.type === "group") {
1474
+ counts.groups++;
1475
+ if (node.prompt) counts.prompts++;
1476
+ stack.push(...node.children);
1477
+ continue;
1478
+ }
1479
+ counts.snippets++;
1480
+ for (const beat of node.beats ?? []) {
1481
+ counts.beats++;
1482
+ if (beat.kind === "gameEvent") counts.gameEvents++;
1483
+ }
1484
+ }
1485
+ }
1486
+ function describeBundle(bundle) {
1487
+ const counts = {
1488
+ scenes: 0,
1489
+ blocks: 0,
1490
+ groups: 0,
1491
+ snippets: 0,
1492
+ beats: 0,
1493
+ prompts: 0,
1494
+ gameEvents: 0,
1495
+ cast: bundle.cast?.length ?? 0
1496
+ };
1497
+ const addresses = [];
1498
+ const sceneProps = [];
1499
+ for (const scene of Object.values(bundle.scenes)) {
1500
+ counts.scenes++;
1501
+ const gameId = (0, import_model2.effectiveGameId)(scene);
1502
+ addresses.push({
1503
+ gameId,
1504
+ name: scene.name,
1505
+ blocks: scene.blocks.map((b) => ({ gameId: (0, import_model2.effectiveGameId)(b), name: b.name }))
1506
+ });
1507
+ for (const block of scene.blocks) countBlock(block, counts);
1508
+ if (scene.sceneProps?.length) {
1509
+ sceneProps.push({ gameId, properties: scene.sceneProps.map((d) => summariseProperty(d, false)) });
1510
+ }
1511
+ }
1512
+ const hostScopes = (bundle.scopeRegistry?.scopes ?? []).map((s) => ({
1513
+ token: s.token,
1514
+ writable: s.writable ?? true,
1515
+ opaque: s.declarations === void 0,
1516
+ // A host scope's values live outside the story, so "shared" is not a choice
1517
+ // its declarations make; they are world-wide by nature.
1518
+ properties: (s.declarations ?? []).map((d) => summariseProperty(d, true))
1519
+ }));
1520
+ const gameData = Object.entries(bundle.gameDataFields ?? {}).filter(([, fields]) => (fields?.length ?? 0) > 0).map(([kind, fields]) => ({
1521
+ kind,
1522
+ fields: (fields ?? []).map(summariseField)
1523
+ }));
1524
+ return {
1525
+ identity: {
1526
+ schema: bundle.schema,
1527
+ project: bundle.content.project,
1528
+ ...bundle.content.version !== void 0 ? { version: bundle.content.version } : {},
1529
+ ...bundle.content.hash !== void 0 ? { hash: bundle.content.hash } : {},
1530
+ ...bundle.content.structureHash !== void 0 ? { structureHash: bundle.content.structureHash } : {},
1531
+ voiced: bundle.voiced,
1532
+ defaultLocale: bundle.locales.default,
1533
+ locales: [...bundle.locales.included],
1534
+ // Absent means "embedded": the back-compat default a bundle written before
1535
+ // the field existed relies on.
1536
+ localisation: bundle.localisation?.mode ?? "embedded",
1537
+ sourceDebug: bundle.localisation?.sourceDebug ?? false
1538
+ },
1539
+ addresses,
1540
+ hostScopes,
1541
+ properties: {
1542
+ patter: (bundle.properties ?? []).map((d) => summariseProperty(d, true)),
1543
+ scene: sceneProps
1544
+ },
1545
+ gameData,
1546
+ counts
1547
+ };
1548
+ }
1549
+
1445
1550
  // src/gamedata.ts
1446
1551
  function gameDataFields(bundle, kind) {
1447
1552
  return bundle.gameDataFields?.[kind] ?? [];
@@ -1464,6 +1569,7 @@ function effectiveGameData(fields, node) {
1464
1569
  Engine,
1465
1570
  Flow,
1466
1571
  buildTagIndex,
1572
+ describeBundle,
1467
1573
  effectiveGameData,
1468
1574
  gameDataFields,
1469
1575
  gameDataValue