@storylet-studio/model 0.2.0 → 0.4.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
@@ -23,9 +23,13 @@ __export(index_exports, {
23
23
  BOX_SCHEMA: () => BOX_SCHEMA,
24
24
  BUNDLE_EXTENSION: () => BUNDLE_EXTENSION,
25
25
  BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
26
+ CONTRACTS_DIR: () => CONTRACTS_DIR,
27
+ CONTRACT_SCHEMA: () => CONTRACT_SCHEMA,
26
28
  DECK_SCHEMA: () => DECK_SCHEMA,
29
+ DEFAULT_PLAY_RUNG: () => DEFAULT_PLAY_RUNG,
27
30
  FURNITURE_COLOURS: () => FURNITURE_COLOURS,
28
31
  HANDS_SCHEMA: () => HANDS_SCHEMA,
32
+ MAP_SCHEMA: () => MAP_SCHEMA,
29
33
  NOTES_SCHEMA: () => NOTES_SCHEMA,
30
34
  PLACE_GROUP: () => PLACE_GROUP,
31
35
  PROJECT_FOLDER_EXTENSION: () => PROJECT_FOLDER_EXTENSION,
@@ -37,12 +41,15 @@ __export(index_exports, {
37
41
  SPATIAL: () => SPATIAL,
38
42
  TAGS_SCHEMA: () => TAGS_SCHEMA,
39
43
  VIEW_SCHEMA: () => VIEW_SCHEMA,
44
+ ambiguousValueAddressMessage: () => ambiguousValueAddressMessage,
40
45
  backgroundsOf: () => backgroundsOf,
41
46
  bindHand: () => bindHand,
42
47
  bundleAssetPath: () => bundleAssetPath,
43
48
  byDisplayOrder: () => byDisplayOrder,
44
49
  centroid: () => centroid,
45
50
  commentsOf: () => commentsOf,
51
+ contractPropertyPath: () => contractPropertyPath,
52
+ contractPropertyType: () => contractPropertyType,
46
53
  droppedRect: () => droppedRect,
47
54
  effectiveGameId: () => effectiveGameId,
48
55
  framesOf: () => framesOf,
@@ -52,6 +59,7 @@ __export(index_exports, {
52
59
  handBinding: () => handBinding,
53
60
  inferDeclFromWrite: () => inferDeclFromWrite,
54
61
  isCaseOnlyPropertyName: () => import_expr.isCaseOnlyPropertyName,
62
+ isHoleRef: () => isHoleRef,
55
63
  isSpatial: () => isSpatial,
56
64
  isValidGameId: () => isValidGameId,
57
65
  isValidPropertyName: () => import_expr.isValidPropertyName,
@@ -59,6 +67,7 @@ __export(index_exports, {
59
67
  markOf: () => markOf,
60
68
  marksOn: () => marksOn,
61
69
  openThreadCounts: () => openThreadCounts,
70
+ parseHoleRef: () => parseHoleRef,
62
71
  pointInPolygon: () => pointInPolygon,
63
72
  polygonBounds: () => polygonBounds,
64
73
  polygonOf: () => polygonOf,
@@ -67,7 +76,9 @@ __export(index_exports, {
67
76
  spatialOf: () => spatialOf,
68
77
  stacked: () => stacked,
69
78
  threadsFor: () => threadsFor,
79
+ turnSpan: () => turnSpan,
70
80
  unbindHand: () => unbindHand,
81
+ valueAddresses: () => valueAddresses,
71
82
  withBackgrounds: () => withBackgrounds,
72
83
  withPolygon: () => withPolygon,
73
84
  withSpatialGroup: () => withSpatialGroup,
@@ -467,6 +478,41 @@ function effectiveGameId(entity) {
467
478
  const fromTitle = entity.title ? gameIdify(entity.title) : "";
468
479
  return fromTitle || entity.id;
469
480
  }
481
+ function valueAddresses(bundle) {
482
+ const tags = [];
483
+ for (const box of bundle.boxes) {
484
+ const boxGameId = effectiveGameId(box);
485
+ for (const group of box.tagGroups) {
486
+ for (const tag of group.tags) {
487
+ const gameId = effectiveGameId(tag);
488
+ tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });
489
+ }
490
+ }
491
+ }
492
+ const forms = /* @__PURE__ */ new Map();
493
+ for (const tag of tags) {
494
+ const list = forms.get(tag.gameId) ?? [];
495
+ if (!list.includes(tag.qualified)) list.push(tag.qualified);
496
+ forms.set(tag.gameId, list);
497
+ }
498
+ const print = /* @__PURE__ */ new Map();
499
+ const accept = /* @__PURE__ */ new Map();
500
+ const repeated = /* @__PURE__ */ new Map();
501
+ for (const tag of tags) {
502
+ const candidates = forms.get(tag.gameId) ?? [tag.qualified];
503
+ const ambiguous = candidates.length > 1;
504
+ print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);
505
+ if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);
506
+ if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);
507
+ if (ambiguous) repeated.set(tag.gameId, candidates);
508
+ }
509
+ return { print, accept, repeated };
510
+ }
511
+ function ambiguousValueAddressMessage(segment, name, candidates) {
512
+ const forms = candidates.map((q) => `"value.${q}.${name}"`);
513
+ const list = forms.length <= 1 ? forms[0] ?? "" : `${forms.slice(0, -1).join(", ")} or ${forms[forms.length - 1]}`;
514
+ return `"value.${segment}.${name}" names a tag in ${candidates.length} boxes; write ${list}`;
515
+ }
470
516
  function freeGameId(base, taken) {
471
517
  let gameId = base;
472
518
  for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;
@@ -480,8 +526,32 @@ function freeTitle(base, taken) {
480
526
  for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;
481
527
  return title;
482
528
  }
529
+ function turnSpan(turns, seconds, long = false) {
530
+ const total = Math.max(0, Math.round(turns * seconds));
531
+ const say = (n, short, one, many) => long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;
532
+ if (total < 60) return long ? say(total, "s", "second", "seconds") : `${total}s`;
533
+ if (total < 3600) {
534
+ const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;
535
+ return say(minutes, "min", "minute", "minutes");
536
+ }
537
+ let hours = Math.floor(total / 3600);
538
+ let rest = Math.round(total % 3600 / 60);
539
+ if (rest === 60) {
540
+ hours += 1;
541
+ rest = 0;
542
+ }
543
+ const said = say(hours, "hr", "hour", "hours");
544
+ return rest === 0 ? said : `${said} ${say(rest, "min", "minute", "minutes")}`;
545
+ }
483
546
  var PLACE_GROUP = "place";
547
+ var HOLE_REF = /^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/;
548
+ var isHoleRef = (value) => value.startsWith("@");
549
+ var parseHoleRef = (value) => {
550
+ const m = HOLE_REF.exec(value);
551
+ return m === null ? void 0 : { scope: m[1], name: m[2] };
552
+ };
484
553
  var BUNDLE_SCHEMA = "storylets/bundle@0";
554
+ var DEFAULT_PLAY_RUNG = "solo";
485
555
  var SAVE_SCHEMA = "storylets/save@1";
486
556
  var SAVEFILE_SCHEMA = "storylets/savefile@1";
487
557
  var PROJECT_FOLDER_EXTENSION = ".storylets";
@@ -493,35 +563,62 @@ var SHARD_EXTENSIONS = {
493
563
  tags: ".storylettags",
494
564
  hands: ".storylethands",
495
565
  deck: ".storyletdeck",
496
- /** The arrangement layer: where things SIT, never what they are. Its own shard
497
- * because positions churn (an afternoon of tidying a canvas touches every
498
- * card) and content does not, so a designer arranging and a writer editing
499
- * never collide on one file (design/graphical-views.md section 1.2). */
566
+ /** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's
567
+ * node canvas and the furniture drawn round them. Its own shard because
568
+ * positions churn (an afternoon of tidying a canvas touches every card) and
569
+ * content does not, so a designer arranging and a writer editing never
570
+ * collide on one file (design/graphical-views.md section 1.2). */
500
571
  view: ".storyletview",
572
+ /** The DESIGNER's map: where a box's hands stand in space, and the furniture
573
+ * round them. One per box, beside the view shard.
574
+ *
575
+ * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1
576
+ * point 5) because the two halves stopped having one owner. A hand's
577
+ * position ships in the bundle's `maps` block (4.3) and is where a venue's
578
+ * kiosk stands, so it is SHAPE, which a server's author key may not change;
579
+ * the canvases are the author's own working drawing and never leave the
580
+ * project folder. One file could not be both. */
581
+ map: ".storyletmap",
501
582
  /** Threaded comments: content-ADJACENT, so neither in a content shard (a
502
583
  * writer's deck edit must not conflict with a reviewer's comment) nor in the
503
584
  * arrangement sidecar (this is not where anything sits). One per box,
504
585
  * id-keyed (design/annotation.md). Documentation NOTES used to share this
505
586
  * file and were retired: `purpose` already says why a thing exists, and
506
587
  * Patterpad's typed routing has no destination here. */
507
- notes: ".storyletnotes"
588
+ notes: ".storyletnotes",
589
+ /** An installation contract: what a VENUE depends on, one file per
590
+ * installation in `contracts/` at the project root
591
+ * (design/engine-server.md 4.11). Its own shard, and its own folder, for the
592
+ * walkthrough's reason (Reboot 7.5, S4): a different owner, a different
593
+ * change rate, and a merge that must never collide with the author's edits,
594
+ * since the server always wins its own file. */
595
+ contract: ".storyletcontract"
508
596
  };
597
+ var CONTRACTS_DIR = "contracts";
509
598
  var PROJECT_SCHEMA = "storylets/project@0";
510
599
  var BOX_SCHEMA = "storylets/box@0";
511
600
  var TAGS_SCHEMA = "storylets/tags@0";
512
601
  var HANDS_SCHEMA = "storylets/hands@0";
513
602
  var DECK_SCHEMA = "storylets/deck@0";
514
603
  var VIEW_SCHEMA = "storylets/view@0";
604
+ var MAP_SCHEMA = "storylets/map@0";
515
605
  var NOTES_SCHEMA = "storylets/notes@0";
606
+ var CONTRACT_SCHEMA = "storylets/contract@0";
607
+ var contractPropertyPath = (p) => typeof p === "string" ? p : p.path;
608
+ var contractPropertyType = (p) => typeof p === "string" ? void 0 : p.type;
516
609
  var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
517
610
  // Annotate the CommonJS export names for ESM import in node:
518
611
  0 && (module.exports = {
519
612
  BOX_SCHEMA,
520
613
  BUNDLE_EXTENSION,
521
614
  BUNDLE_SCHEMA,
615
+ CONTRACTS_DIR,
616
+ CONTRACT_SCHEMA,
522
617
  DECK_SCHEMA,
618
+ DEFAULT_PLAY_RUNG,
523
619
  FURNITURE_COLOURS,
524
620
  HANDS_SCHEMA,
621
+ MAP_SCHEMA,
525
622
  NOTES_SCHEMA,
526
623
  PLACE_GROUP,
527
624
  PROJECT_FOLDER_EXTENSION,
@@ -533,12 +630,15 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
533
630
  SPATIAL,
534
631
  TAGS_SCHEMA,
535
632
  VIEW_SCHEMA,
633
+ ambiguousValueAddressMessage,
536
634
  backgroundsOf,
537
635
  bindHand,
538
636
  bundleAssetPath,
539
637
  byDisplayOrder,
540
638
  centroid,
541
639
  commentsOf,
640
+ contractPropertyPath,
641
+ contractPropertyType,
542
642
  droppedRect,
543
643
  effectiveGameId,
544
644
  framesOf,
@@ -548,6 +648,7 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
548
648
  handBinding,
549
649
  inferDeclFromWrite,
550
650
  isCaseOnlyPropertyName,
651
+ isHoleRef,
551
652
  isSpatial,
552
653
  isValidGameId,
553
654
  isValidPropertyName,
@@ -555,6 +656,7 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
555
656
  markOf,
556
657
  marksOn,
557
658
  openThreadCounts,
659
+ parseHoleRef,
558
660
  pointInPolygon,
559
661
  polygonBounds,
560
662
  polygonOf,
@@ -563,7 +665,9 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
563
665
  spatialOf,
564
666
  stacked,
565
667
  threadsFor,
668
+ turnSpan,
566
669
  unbindHand,
670
+ valueAddresses,
567
671
  withBackgrounds,
568
672
  withPolygon,
569
673
  withSpatialGroup,