@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.js CHANGED
@@ -394,6 +394,41 @@ function effectiveGameId(entity) {
394
394
  const fromTitle = entity.title ? gameIdify(entity.title) : "";
395
395
  return fromTitle || entity.id;
396
396
  }
397
+ function valueAddresses(bundle) {
398
+ const tags = [];
399
+ for (const box of bundle.boxes) {
400
+ const boxGameId = effectiveGameId(box);
401
+ for (const group of box.tagGroups) {
402
+ for (const tag of group.tags) {
403
+ const gameId = effectiveGameId(tag);
404
+ tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });
405
+ }
406
+ }
407
+ }
408
+ const forms = /* @__PURE__ */ new Map();
409
+ for (const tag of tags) {
410
+ const list = forms.get(tag.gameId) ?? [];
411
+ if (!list.includes(tag.qualified)) list.push(tag.qualified);
412
+ forms.set(tag.gameId, list);
413
+ }
414
+ const print = /* @__PURE__ */ new Map();
415
+ const accept = /* @__PURE__ */ new Map();
416
+ const repeated = /* @__PURE__ */ new Map();
417
+ for (const tag of tags) {
418
+ const candidates = forms.get(tag.gameId) ?? [tag.qualified];
419
+ const ambiguous = candidates.length > 1;
420
+ print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);
421
+ if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);
422
+ if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);
423
+ if (ambiguous) repeated.set(tag.gameId, candidates);
424
+ }
425
+ return { print, accept, repeated };
426
+ }
427
+ function ambiguousValueAddressMessage(segment, name, candidates) {
428
+ const forms = candidates.map((q) => `"value.${q}.${name}"`);
429
+ const list = forms.length <= 1 ? forms[0] ?? "" : `${forms.slice(0, -1).join(", ")} or ${forms[forms.length - 1]}`;
430
+ return `"value.${segment}.${name}" names a tag in ${candidates.length} boxes; write ${list}`;
431
+ }
397
432
  function freeGameId(base, taken) {
398
433
  let gameId = base;
399
434
  for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;
@@ -407,8 +442,32 @@ function freeTitle(base, taken) {
407
442
  for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;
408
443
  return title;
409
444
  }
445
+ function turnSpan(turns, seconds, long = false) {
446
+ const total = Math.max(0, Math.round(turns * seconds));
447
+ const say = (n, short, one, many) => long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;
448
+ if (total < 60) return long ? say(total, "s", "second", "seconds") : `${total}s`;
449
+ if (total < 3600) {
450
+ const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;
451
+ return say(minutes, "min", "minute", "minutes");
452
+ }
453
+ let hours = Math.floor(total / 3600);
454
+ let rest = Math.round(total % 3600 / 60);
455
+ if (rest === 60) {
456
+ hours += 1;
457
+ rest = 0;
458
+ }
459
+ const said = say(hours, "hr", "hour", "hours");
460
+ return rest === 0 ? said : `${said} ${say(rest, "min", "minute", "minutes")}`;
461
+ }
410
462
  var PLACE_GROUP = "place";
463
+ var HOLE_REF = /^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/;
464
+ var isHoleRef = (value) => value.startsWith("@");
465
+ var parseHoleRef = (value) => {
466
+ const m = HOLE_REF.exec(value);
467
+ return m === null ? void 0 : { scope: m[1], name: m[2] };
468
+ };
411
469
  var BUNDLE_SCHEMA = "storylets/bundle@0";
470
+ var DEFAULT_PLAY_RUNG = "solo";
412
471
  var SAVE_SCHEMA = "storylets/save@1";
413
472
  var SAVEFILE_SCHEMA = "storylets/savefile@1";
414
473
  var PROJECT_FOLDER_EXTENSION = ".storylets";
@@ -420,34 +479,61 @@ var SHARD_EXTENSIONS = {
420
479
  tags: ".storylettags",
421
480
  hands: ".storylethands",
422
481
  deck: ".storyletdeck",
423
- /** The arrangement layer: where things SIT, never what they are. Its own shard
424
- * because positions churn (an afternoon of tidying a canvas touches every
425
- * card) and content does not, so a designer arranging and a writer editing
426
- * never collide on one file (design/graphical-views.md section 1.2). */
482
+ /** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's
483
+ * node canvas and the furniture drawn round them. Its own shard because
484
+ * positions churn (an afternoon of tidying a canvas touches every card) and
485
+ * content does not, so a designer arranging and a writer editing never
486
+ * collide on one file (design/graphical-views.md section 1.2). */
427
487
  view: ".storyletview",
488
+ /** The DESIGNER's map: where a box's hands stand in space, and the furniture
489
+ * round them. One per box, beside the view shard.
490
+ *
491
+ * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1
492
+ * point 5) because the two halves stopped having one owner. A hand's
493
+ * position ships in the bundle's `maps` block (4.3) and is where a venue's
494
+ * kiosk stands, so it is SHAPE, which a server's author key may not change;
495
+ * the canvases are the author's own working drawing and never leave the
496
+ * project folder. One file could not be both. */
497
+ map: ".storyletmap",
428
498
  /** Threaded comments: content-ADJACENT, so neither in a content shard (a
429
499
  * writer's deck edit must not conflict with a reviewer's comment) nor in the
430
500
  * arrangement sidecar (this is not where anything sits). One per box,
431
501
  * id-keyed (design/annotation.md). Documentation NOTES used to share this
432
502
  * file and were retired: `purpose` already says why a thing exists, and
433
503
  * Patterpad's typed routing has no destination here. */
434
- notes: ".storyletnotes"
504
+ notes: ".storyletnotes",
505
+ /** An installation contract: what a VENUE depends on, one file per
506
+ * installation in `contracts/` at the project root
507
+ * (design/engine-server.md 4.11). Its own shard, and its own folder, for the
508
+ * walkthrough's reason (Reboot 7.5, S4): a different owner, a different
509
+ * change rate, and a merge that must never collide with the author's edits,
510
+ * since the server always wins its own file. */
511
+ contract: ".storyletcontract"
435
512
  };
513
+ var CONTRACTS_DIR = "contracts";
436
514
  var PROJECT_SCHEMA = "storylets/project@0";
437
515
  var BOX_SCHEMA = "storylets/box@0";
438
516
  var TAGS_SCHEMA = "storylets/tags@0";
439
517
  var HANDS_SCHEMA = "storylets/hands@0";
440
518
  var DECK_SCHEMA = "storylets/deck@0";
441
519
  var VIEW_SCHEMA = "storylets/view@0";
520
+ var MAP_SCHEMA = "storylets/map@0";
442
521
  var NOTES_SCHEMA = "storylets/notes@0";
522
+ var CONTRACT_SCHEMA = "storylets/contract@0";
523
+ var contractPropertyPath = (p) => typeof p === "string" ? p : p.path;
524
+ var contractPropertyType = (p) => typeof p === "string" ? void 0 : p.type;
443
525
  var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
444
526
  export {
445
527
  BOX_SCHEMA,
446
528
  BUNDLE_EXTENSION,
447
529
  BUNDLE_SCHEMA,
530
+ CONTRACTS_DIR,
531
+ CONTRACT_SCHEMA,
448
532
  DECK_SCHEMA,
533
+ DEFAULT_PLAY_RUNG,
449
534
  FURNITURE_COLOURS,
450
535
  HANDS_SCHEMA,
536
+ MAP_SCHEMA,
451
537
  NOTES_SCHEMA,
452
538
  PLACE_GROUP,
453
539
  PROJECT_FOLDER_EXTENSION,
@@ -459,12 +545,15 @@ export {
459
545
  SPATIAL,
460
546
  TAGS_SCHEMA,
461
547
  VIEW_SCHEMA,
548
+ ambiguousValueAddressMessage,
462
549
  backgroundsOf,
463
550
  bindHand,
464
551
  bundleAssetPath,
465
552
  byDisplayOrder,
466
553
  centroid,
467
554
  commentsOf,
555
+ contractPropertyPath,
556
+ contractPropertyType,
468
557
  droppedRect,
469
558
  effectiveGameId,
470
559
  framesOf,
@@ -474,6 +563,7 @@ export {
474
563
  handBinding,
475
564
  inferDeclFromWrite,
476
565
  isCaseOnlyPropertyName,
566
+ isHoleRef,
477
567
  isSpatial,
478
568
  isValidGameId,
479
569
  isValidPropertyName,
@@ -481,6 +571,7 @@ export {
481
571
  markOf,
482
572
  marksOn,
483
573
  openThreadCounts,
574
+ parseHoleRef,
484
575
  pointInPolygon,
485
576
  polygonBounds,
486
577
  polygonOf,
@@ -489,7 +580,9 @@ export {
489
580
  spatialOf,
490
581
  stacked,
491
582
  threadsFor,
583
+ turnSpan,
492
584
  unbindHand,
585
+ valueAddresses,
493
586
  withBackgrounds,
494
587
  withPolygon,
495
588
  withSpatialGroup,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storylet-studio/model",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Storylets data-model types: source shards (project / box / tags / hands / deck), the compiled bundle, the save envelope. The shape source-of-truth.",
5
5
  "type": "module",
6
6
  "license": "MIT",