@storylet-studio/model 0.2.0 → 0.3.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 +91 -1
- package/dist/index.d.cts +338 -8
- package/dist/index.d.ts +338 -8
- package/dist/index.js +81 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,10 @@ __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,
|
|
29
32
|
NOTES_SCHEMA: () => NOTES_SCHEMA,
|
|
@@ -37,12 +40,15 @@ __export(index_exports, {
|
|
|
37
40
|
SPATIAL: () => SPATIAL,
|
|
38
41
|
TAGS_SCHEMA: () => TAGS_SCHEMA,
|
|
39
42
|
VIEW_SCHEMA: () => VIEW_SCHEMA,
|
|
43
|
+
ambiguousValueAddressMessage: () => ambiguousValueAddressMessage,
|
|
40
44
|
backgroundsOf: () => backgroundsOf,
|
|
41
45
|
bindHand: () => bindHand,
|
|
42
46
|
bundleAssetPath: () => bundleAssetPath,
|
|
43
47
|
byDisplayOrder: () => byDisplayOrder,
|
|
44
48
|
centroid: () => centroid,
|
|
45
49
|
commentsOf: () => commentsOf,
|
|
50
|
+
contractPropertyPath: () => contractPropertyPath,
|
|
51
|
+
contractPropertyType: () => contractPropertyType,
|
|
46
52
|
droppedRect: () => droppedRect,
|
|
47
53
|
effectiveGameId: () => effectiveGameId,
|
|
48
54
|
framesOf: () => framesOf,
|
|
@@ -52,6 +58,7 @@ __export(index_exports, {
|
|
|
52
58
|
handBinding: () => handBinding,
|
|
53
59
|
inferDeclFromWrite: () => inferDeclFromWrite,
|
|
54
60
|
isCaseOnlyPropertyName: () => import_expr.isCaseOnlyPropertyName,
|
|
61
|
+
isHoleRef: () => isHoleRef,
|
|
55
62
|
isSpatial: () => isSpatial,
|
|
56
63
|
isValidGameId: () => isValidGameId,
|
|
57
64
|
isValidPropertyName: () => import_expr.isValidPropertyName,
|
|
@@ -59,6 +66,7 @@ __export(index_exports, {
|
|
|
59
66
|
markOf: () => markOf,
|
|
60
67
|
marksOn: () => marksOn,
|
|
61
68
|
openThreadCounts: () => openThreadCounts,
|
|
69
|
+
parseHoleRef: () => parseHoleRef,
|
|
62
70
|
pointInPolygon: () => pointInPolygon,
|
|
63
71
|
polygonBounds: () => polygonBounds,
|
|
64
72
|
polygonOf: () => polygonOf,
|
|
@@ -67,7 +75,9 @@ __export(index_exports, {
|
|
|
67
75
|
spatialOf: () => spatialOf,
|
|
68
76
|
stacked: () => stacked,
|
|
69
77
|
threadsFor: () => threadsFor,
|
|
78
|
+
turnSpan: () => turnSpan,
|
|
70
79
|
unbindHand: () => unbindHand,
|
|
80
|
+
valueAddresses: () => valueAddresses,
|
|
71
81
|
withBackgrounds: () => withBackgrounds,
|
|
72
82
|
withPolygon: () => withPolygon,
|
|
73
83
|
withSpatialGroup: () => withSpatialGroup,
|
|
@@ -467,6 +477,41 @@ function effectiveGameId(entity) {
|
|
|
467
477
|
const fromTitle = entity.title ? gameIdify(entity.title) : "";
|
|
468
478
|
return fromTitle || entity.id;
|
|
469
479
|
}
|
|
480
|
+
function valueAddresses(bundle) {
|
|
481
|
+
const tags = [];
|
|
482
|
+
for (const box of bundle.boxes) {
|
|
483
|
+
const boxGameId = effectiveGameId(box);
|
|
484
|
+
for (const group of box.tagGroups) {
|
|
485
|
+
for (const tag of group.tags) {
|
|
486
|
+
const gameId = effectiveGameId(tag);
|
|
487
|
+
tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const forms = /* @__PURE__ */ new Map();
|
|
492
|
+
for (const tag of tags) {
|
|
493
|
+
const list = forms.get(tag.gameId) ?? [];
|
|
494
|
+
if (!list.includes(tag.qualified)) list.push(tag.qualified);
|
|
495
|
+
forms.set(tag.gameId, list);
|
|
496
|
+
}
|
|
497
|
+
const print = /* @__PURE__ */ new Map();
|
|
498
|
+
const accept = /* @__PURE__ */ new Map();
|
|
499
|
+
const repeated = /* @__PURE__ */ new Map();
|
|
500
|
+
for (const tag of tags) {
|
|
501
|
+
const candidates = forms.get(tag.gameId) ?? [tag.qualified];
|
|
502
|
+
const ambiguous = candidates.length > 1;
|
|
503
|
+
print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);
|
|
504
|
+
if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);
|
|
505
|
+
if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);
|
|
506
|
+
if (ambiguous) repeated.set(tag.gameId, candidates);
|
|
507
|
+
}
|
|
508
|
+
return { print, accept, repeated };
|
|
509
|
+
}
|
|
510
|
+
function ambiguousValueAddressMessage(segment, name, candidates) {
|
|
511
|
+
const forms = candidates.map((q) => `"value.${q}.${name}"`);
|
|
512
|
+
const list = forms.length <= 1 ? forms[0] ?? "" : `${forms.slice(0, -1).join(", ")} or ${forms[forms.length - 1]}`;
|
|
513
|
+
return `"value.${segment}.${name}" names a tag in ${candidates.length} boxes; write ${list}`;
|
|
514
|
+
}
|
|
470
515
|
function freeGameId(base, taken) {
|
|
471
516
|
let gameId = base;
|
|
472
517
|
for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;
|
|
@@ -480,8 +525,32 @@ function freeTitle(base, taken) {
|
|
|
480
525
|
for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;
|
|
481
526
|
return title;
|
|
482
527
|
}
|
|
528
|
+
function turnSpan(turns, seconds, long = false) {
|
|
529
|
+
const total = Math.max(0, Math.round(turns * seconds));
|
|
530
|
+
const say = (n, short, one, many) => long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;
|
|
531
|
+
if (total < 60) return long ? say(total, "s", "second", "seconds") : `${total}s`;
|
|
532
|
+
if (total < 3600) {
|
|
533
|
+
const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;
|
|
534
|
+
return say(minutes, "min", "minute", "minutes");
|
|
535
|
+
}
|
|
536
|
+
let hours = Math.floor(total / 3600);
|
|
537
|
+
let rest = Math.round(total % 3600 / 60);
|
|
538
|
+
if (rest === 60) {
|
|
539
|
+
hours += 1;
|
|
540
|
+
rest = 0;
|
|
541
|
+
}
|
|
542
|
+
const said = say(hours, "hr", "hour", "hours");
|
|
543
|
+
return rest === 0 ? said : `${said} ${say(rest, "min", "minute", "minutes")}`;
|
|
544
|
+
}
|
|
483
545
|
var PLACE_GROUP = "place";
|
|
546
|
+
var HOLE_REF = /^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/;
|
|
547
|
+
var isHoleRef = (value) => value.startsWith("@");
|
|
548
|
+
var parseHoleRef = (value) => {
|
|
549
|
+
const m = HOLE_REF.exec(value);
|
|
550
|
+
return m === null ? void 0 : { scope: m[1], name: m[2] };
|
|
551
|
+
};
|
|
484
552
|
var BUNDLE_SCHEMA = "storylets/bundle@0";
|
|
553
|
+
var DEFAULT_PLAY_RUNG = "solo";
|
|
485
554
|
var SAVE_SCHEMA = "storylets/save@1";
|
|
486
555
|
var SAVEFILE_SCHEMA = "storylets/savefile@1";
|
|
487
556
|
var PROJECT_FOLDER_EXTENSION = ".storylets";
|
|
@@ -504,8 +573,16 @@ var SHARD_EXTENSIONS = {
|
|
|
504
573
|
* id-keyed (design/annotation.md). Documentation NOTES used to share this
|
|
505
574
|
* file and were retired: `purpose` already says why a thing exists, and
|
|
506
575
|
* Patterpad's typed routing has no destination here. */
|
|
507
|
-
notes: ".storyletnotes"
|
|
576
|
+
notes: ".storyletnotes",
|
|
577
|
+
/** An installation contract: what a VENUE depends on, one file per
|
|
578
|
+
* installation in `contracts/` at the project root
|
|
579
|
+
* (design/engine-server.md 4.11). Its own shard, and its own folder, for the
|
|
580
|
+
* walkthrough's reason (Reboot 7.5, S4): a different owner, a different
|
|
581
|
+
* change rate, and a merge that must never collide with the author's edits,
|
|
582
|
+
* since the server always wins its own file. */
|
|
583
|
+
contract: ".storyletcontract"
|
|
508
584
|
};
|
|
585
|
+
var CONTRACTS_DIR = "contracts";
|
|
509
586
|
var PROJECT_SCHEMA = "storylets/project@0";
|
|
510
587
|
var BOX_SCHEMA = "storylets/box@0";
|
|
511
588
|
var TAGS_SCHEMA = "storylets/tags@0";
|
|
@@ -513,13 +590,19 @@ var HANDS_SCHEMA = "storylets/hands@0";
|
|
|
513
590
|
var DECK_SCHEMA = "storylets/deck@0";
|
|
514
591
|
var VIEW_SCHEMA = "storylets/view@0";
|
|
515
592
|
var NOTES_SCHEMA = "storylets/notes@0";
|
|
593
|
+
var CONTRACT_SCHEMA = "storylets/contract@0";
|
|
594
|
+
var contractPropertyPath = (p) => typeof p === "string" ? p : p.path;
|
|
595
|
+
var contractPropertyType = (p) => typeof p === "string" ? void 0 : p.type;
|
|
516
596
|
var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
517
597
|
// Annotate the CommonJS export names for ESM import in node:
|
|
518
598
|
0 && (module.exports = {
|
|
519
599
|
BOX_SCHEMA,
|
|
520
600
|
BUNDLE_EXTENSION,
|
|
521
601
|
BUNDLE_SCHEMA,
|
|
602
|
+
CONTRACTS_DIR,
|
|
603
|
+
CONTRACT_SCHEMA,
|
|
522
604
|
DECK_SCHEMA,
|
|
605
|
+
DEFAULT_PLAY_RUNG,
|
|
523
606
|
FURNITURE_COLOURS,
|
|
524
607
|
HANDS_SCHEMA,
|
|
525
608
|
NOTES_SCHEMA,
|
|
@@ -533,12 +616,15 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
|
533
616
|
SPATIAL,
|
|
534
617
|
TAGS_SCHEMA,
|
|
535
618
|
VIEW_SCHEMA,
|
|
619
|
+
ambiguousValueAddressMessage,
|
|
536
620
|
backgroundsOf,
|
|
537
621
|
bindHand,
|
|
538
622
|
bundleAssetPath,
|
|
539
623
|
byDisplayOrder,
|
|
540
624
|
centroid,
|
|
541
625
|
commentsOf,
|
|
626
|
+
contractPropertyPath,
|
|
627
|
+
contractPropertyType,
|
|
542
628
|
droppedRect,
|
|
543
629
|
effectiveGameId,
|
|
544
630
|
framesOf,
|
|
@@ -548,6 +634,7 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
|
548
634
|
handBinding,
|
|
549
635
|
inferDeclFromWrite,
|
|
550
636
|
isCaseOnlyPropertyName,
|
|
637
|
+
isHoleRef,
|
|
551
638
|
isSpatial,
|
|
552
639
|
isValidGameId,
|
|
553
640
|
isValidPropertyName,
|
|
@@ -555,6 +642,7 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
|
555
642
|
markOf,
|
|
556
643
|
marksOn,
|
|
557
644
|
openThreadCounts,
|
|
645
|
+
parseHoleRef,
|
|
558
646
|
pointInPolygon,
|
|
559
647
|
polygonBounds,
|
|
560
648
|
polygonOf,
|
|
@@ -563,7 +651,9 @@ var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
|
563
651
|
spatialOf,
|
|
564
652
|
stacked,
|
|
565
653
|
threadsFor,
|
|
654
|
+
turnSpan,
|
|
566
655
|
unbindHand,
|
|
656
|
+
valueAddresses,
|
|
567
657
|
withBackgrounds,
|
|
568
658
|
withPolygon,
|
|
569
659
|
withSpatialGroup,
|
package/dist/index.d.cts
CHANGED
|
@@ -409,6 +409,25 @@ interface PropertyDecl {
|
|
|
409
409
|
* the game's own state, always engine-level, never per-flow.
|
|
410
410
|
*/
|
|
411
411
|
shared?: boolean;
|
|
412
|
+
/**
|
|
413
|
+
* The durability axis (design/engine-server.md 4.2), valid wherever `shared`
|
|
414
|
+
* is valid and orthogonal to it: `shared` says whose value this is WITHIN a
|
|
415
|
+
* run, `durable` says whether the value survives the run at all. A durable
|
|
416
|
+
* shared property is the installation's memory ("trolls defeated since we
|
|
417
|
+
* opened"); a durable per-flow one is the player's pocket (visits,
|
|
418
|
+
* allegiance, what they earned).
|
|
419
|
+
*
|
|
420
|
+
* INERT TO THE RUNTIME. The engine partitions by `shared` alone and never
|
|
421
|
+
* reads this. Durability is what the SERVER does at a run boundary: it reads
|
|
422
|
+
* the declarations, lifts the durable values out of the partitions before the
|
|
423
|
+
* world restarts, and writes them back into the fresh engine afterwards,
|
|
424
|
+
* entirely through `getProperty` / `setProperty`.
|
|
425
|
+
*
|
|
426
|
+
* On a `@world` declaration the flag is a validation error, for the reason
|
|
427
|
+
* `shared` is: @world is the game's own state, and how long the game keeps it
|
|
428
|
+
* is the game's business.
|
|
429
|
+
*/
|
|
430
|
+
durable?: boolean;
|
|
412
431
|
purpose?: string;
|
|
413
432
|
}
|
|
414
433
|
/** A card-template field (box-defined). Data for the host; the engine never
|
|
@@ -433,6 +452,32 @@ declare function effectiveGameId(entity: {
|
|
|
433
452
|
title?: string;
|
|
434
453
|
id: string;
|
|
435
454
|
}): string;
|
|
455
|
+
/** The three answers a value address needs, all derived from the bundle. */
|
|
456
|
+
interface ValueAddresses {
|
|
457
|
+
/** Tag internal id -> the owner segment an address PRINTS for it. */
|
|
458
|
+
print: Map<string, string>;
|
|
459
|
+
/** Every owner segment a value address ACCEPTS -> the tag's internal id.
|
|
460
|
+
* Holds the qualified form for every tag and the short form only for a
|
|
461
|
+
* gameId no other tag shares. */
|
|
462
|
+
accept: Map<string, string>;
|
|
463
|
+
/** A tag gameId more than one box uses -> its qualified forms, in bundle
|
|
464
|
+
* order. Empty for the overwhelming majority of projects, and what a
|
|
465
|
+
* refusal lists. */
|
|
466
|
+
repeated: Map<string, string[]>;
|
|
467
|
+
}
|
|
468
|
+
/** The owner segment of every tag in the bundle, both ways round. */
|
|
469
|
+
declare function valueAddresses(bundle: {
|
|
470
|
+
boxes: readonly {
|
|
471
|
+
id: string;
|
|
472
|
+
gameId?: string;
|
|
473
|
+
title?: string;
|
|
474
|
+
tagGroups: readonly TagGroup[];
|
|
475
|
+
}[];
|
|
476
|
+
}): ValueAddresses;
|
|
477
|
+
/** What an ambiguous short-form value address is told: the candidates, in
|
|
478
|
+
* full, because "that names two tags" without them leaves a host reading a
|
|
479
|
+
* bundle it did not write to find out which boxes. */
|
|
480
|
+
declare function ambiguousValueAddressMessage(segment: string, name: string, candidates: readonly string[]): string;
|
|
436
481
|
/**
|
|
437
482
|
* The first free gameId of the form `base`, `base-2`, `base-3`, ... not already
|
|
438
483
|
* in `taken`.
|
|
@@ -473,6 +518,17 @@ declare function byDisplayOrder<T extends {
|
|
|
473
518
|
order?: number;
|
|
474
519
|
}>(items: readonly T[]): T[];
|
|
475
520
|
declare function freeTitle(base: string, taken: ReadonlySet<string>): string;
|
|
521
|
+
/**
|
|
522
|
+
* A count of a TIMED box's turns, said as time (design/engine-server.md 4.8):
|
|
523
|
+
* `turnSpan(30, 60)` is "30 min", and `turnSpan(30, 60, true)` is "30 minutes".
|
|
524
|
+
*
|
|
525
|
+
* One definition, because the conversion appears wherever a designer might
|
|
526
|
+
* otherwise have to do it in their head: the card editor's Redraw field, the
|
|
527
|
+
* box page, the Board's advance buttons, and the coverage report's turn
|
|
528
|
+
* budget. Two of those want the unit spelled out and two want it short, which
|
|
529
|
+
* is the whole of `long`.
|
|
530
|
+
*/
|
|
531
|
+
declare function turnSpan(turns: number, seconds: number, long?: boolean): string;
|
|
476
532
|
interface Outcome<E> {
|
|
477
533
|
id: string;
|
|
478
534
|
gameId?: string;
|
|
@@ -528,6 +584,21 @@ interface Card<E> {
|
|
|
528
584
|
* common case writes one number and "five in the world, one to a customer"
|
|
529
585
|
* is `copies: 1, sharedCopies: 5`. */
|
|
530
586
|
sharedCopies?: number;
|
|
587
|
+
/** Does this card's `redraw: "never"` spend survive the run
|
|
588
|
+
* (design/engine-server.md 4.2)? Absent takes the deck's flag, set here it
|
|
589
|
+
* overrides the deck, exactly as `shared` does. `shared` decides who a
|
|
590
|
+
* spend counts for WITHIN a run; this decides whether it outlives one.
|
|
591
|
+
*
|
|
592
|
+
* Only `"never"` crosses the run boundary, for the reason only `"never"`
|
|
593
|
+
* crosses the flow boundary (shared-scarcity 9.3.2): a finite cooldown is
|
|
594
|
+
* an absolute turn of a box clock, and the clock resets with the run. On
|
|
595
|
+
* any other redraw the flag is a compile warning.
|
|
596
|
+
*
|
|
597
|
+
* INERT TO THE RUNTIME, like the declaration flag: the server lifts the
|
|
598
|
+
* durable spends at run end (per-flow ones from the flow's `never`
|
|
599
|
+
* cooldowns, shared ones from the engine's spent set) and puts them back
|
|
600
|
+
* through `openFlow(id, { restore })` and `markTaken`. */
|
|
601
|
+
durable?: boolean;
|
|
531
602
|
/** Card-template data: field name -> value, validated at publish. */
|
|
532
603
|
fields?: Record<string, ScalarValue>;
|
|
533
604
|
outcomes: Outcome<E>[];
|
|
@@ -543,6 +614,11 @@ interface Deck<E> {
|
|
|
543
614
|
* in it is shared unless the card says otherwise. The container is where
|
|
544
615
|
* Patter puts its own shared-memory flag, and the deck is our container. */
|
|
545
616
|
shared?: boolean;
|
|
617
|
+
/** Every `redraw: "never"` card in this pile is spent for good, past the end
|
|
618
|
+
* of the run, unless the card says otherwise (design/engine-server.md 4.2).
|
|
619
|
+
* The container carries the flag for the reason `shared` is carried here:
|
|
620
|
+
* a pile is what an author reaches for when a rule is true of all of it. */
|
|
621
|
+
durable?: boolean;
|
|
546
622
|
properties: PropertyDecl[];
|
|
547
623
|
cards: Card<E>[];
|
|
548
624
|
}
|
|
@@ -638,6 +714,30 @@ interface TagGroup {
|
|
|
638
714
|
* half of that answer. `home` was a metaphor an author had to learn, and it
|
|
639
715
|
* leaked into hand-edited shards and the docs. */
|
|
640
716
|
declare const PLACE_GROUP = "place";
|
|
717
|
+
/** The scopes a movable hole may be filled from (design/engine-server.md 4.6):
|
|
718
|
+
* the two `boundBy` already allows, plus `@hand` - the asking hand's OWN
|
|
719
|
+
* declared property, resolved before tag composition so a movable hole can
|
|
720
|
+
* never depend on the tags it is choosing. */
|
|
721
|
+
type HoleRefScope = "hand" | "story" | "world";
|
|
722
|
+
/** A parsed hole reference: `@hand.zone` -> `{ scope: "hand", name: "zone" }`. */
|
|
723
|
+
interface HoleRef {
|
|
724
|
+
scope: HoleRefScope;
|
|
725
|
+
name: string;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Is this `chosen` / binding value MEANT as a property reference rather than a
|
|
729
|
+
* tag id?
|
|
730
|
+
*
|
|
731
|
+
* The test is the leading `@` alone, deliberately: a value that starts with
|
|
732
|
+
* one and does not parse is a mistyped reference, which the compiler should
|
|
733
|
+
* name as such, not a tag id that happens to look odd. Tag ids never begin
|
|
734
|
+
* with `@`.
|
|
735
|
+
*/
|
|
736
|
+
declare const isHoleRef: (value: string) => boolean;
|
|
737
|
+
/** Parse a hole reference, or undefined when it is not one. The on-disk form
|
|
738
|
+
* stays a plain string, so the canonical serialiser and the shard merge need
|
|
739
|
+
* no change at all: a hole is still one group name against one value. */
|
|
740
|
+
declare const parseHoleRef: (value: string) => HoleRef | undefined;
|
|
641
741
|
/** A declared kind of hand (schema 2.6): live-inherited, author-side only,
|
|
642
742
|
* never called from game code. One condition governs every instance. */
|
|
643
743
|
interface HandTemplate<E> {
|
|
@@ -649,9 +749,12 @@ interface HandTemplate<E> {
|
|
|
649
749
|
* position). Merges as a per-item value, so id-sorted storage stays
|
|
650
750
|
* merge-clean (Reboot 7.4). */
|
|
651
751
|
order?: number;
|
|
652
|
-
/** Fixed tag bindings: tag group id -> tag id.
|
|
752
|
+
/** Fixed tag bindings: tag group id -> tag id. Literal tags only: what a
|
|
753
|
+
* template FIXES is the same for every instance, and a hole that moves is
|
|
754
|
+
* the instance's own business (`Hand.chosen`, 4.6). */
|
|
653
755
|
bindings?: Record<string, string>;
|
|
654
|
-
/** The holes: tag group ids each instance fills (one tag each
|
|
756
|
+
/** The holes: tag group ids each instance fills (one tag each, or one
|
|
757
|
+
* property reference: 4.6). */
|
|
655
758
|
chooses?: string[];
|
|
656
759
|
/** Shared availability condition, ANDed in (schema 3.1); evaluated per
|
|
657
760
|
* instance against that instance's composed @hand. */
|
|
@@ -663,6 +766,15 @@ interface HandTemplate<E> {
|
|
|
663
766
|
}
|
|
664
767
|
/** A standalone hand's inline rule (schema 2.6): owned by the hand. */
|
|
665
768
|
interface HandRule<E> {
|
|
769
|
+
/**
|
|
770
|
+
* Tag group id -> tag id, or a PROPERTY REFERENCE (`"@hand.zone"`,
|
|
771
|
+
* `"@story.where"`, `"@world.place"`) the runtime resolves at ask time
|
|
772
|
+
* (design/engine-server.md 4.6, the hand that moves). Still a plain string
|
|
773
|
+
* on disk, so the canonical serialiser and the merge are untouched; what
|
|
774
|
+
* widened is the meaning, and `parseHoleRef` is where it is read.
|
|
775
|
+
*
|
|
776
|
+
* `place` is never fillable this way: it is the hand's own name, not an axis.
|
|
777
|
+
*/
|
|
666
778
|
bindings?: Record<string, string>;
|
|
667
779
|
condition?: E;
|
|
668
780
|
slots: number | "unbounded";
|
|
@@ -679,7 +791,17 @@ interface Hand<E> {
|
|
|
679
791
|
purpose?: string;
|
|
680
792
|
/** Hand template id (not gameId). */
|
|
681
793
|
template?: string;
|
|
682
|
-
/**
|
|
794
|
+
/**
|
|
795
|
+
* Template instances: tag group id -> tag id, one per `chooses` hole.
|
|
796
|
+
*
|
|
797
|
+
* A value may instead be a PROPERTY REFERENCE (`"@hand.zone"`,
|
|
798
|
+
* `"@story.where"`, `"@world.place"`), which makes the hole MOVABLE: the
|
|
799
|
+
* runtime resolves the reference at ask time and binds the hole to the tag
|
|
800
|
+
* the value names, so moving the Elder to the forest is `setProperty` and
|
|
801
|
+
* nothing else (design/engine-server.md 4.6). Still a plain string on disk,
|
|
802
|
+
* so the canonical serialiser and the shard merge need no change; read it
|
|
803
|
+
* with `parseHoleRef`.
|
|
804
|
+
*/
|
|
683
805
|
chosen?: Record<string, string>;
|
|
684
806
|
/** Standalone hands: the inline rule. */
|
|
685
807
|
rule?: HandRule<E>;
|
|
@@ -704,6 +826,25 @@ interface Box<E> {
|
|
|
704
826
|
ranking: {
|
|
705
827
|
specificity: boolean;
|
|
706
828
|
};
|
|
829
|
+
/**
|
|
830
|
+
* A TIMED box: its clock counts real time, one turn every `seconds` of the
|
|
831
|
+
* run (design/engine-server.md 4.8). Absent is the ordinary box, whose turn
|
|
832
|
+
* is a play.
|
|
833
|
+
*
|
|
834
|
+
* Two things follow, and only two. In the ENGINE, a play in this box
|
|
835
|
+
* defaults to advancing nothing: `settings.playAdvancesTurns` does not
|
|
836
|
+
* apply, so a designer cannot declare the convention and then forget to
|
|
837
|
+
* switch play-advance off. Everywhere else it is what the tools SAY: the
|
|
838
|
+
* host ticks the box (the runtime has no clock and gains none here), and a
|
|
839
|
+
* card's `redraw: N` reads as N x `seconds`, which the editors, the bundle
|
|
840
|
+
* inspectors and the coverage report spell out rather than leaving a
|
|
841
|
+
* designer to know that 30 meant minutes.
|
|
842
|
+
*
|
|
843
|
+
* The number itself is inert to the runtime, which never reads it.
|
|
844
|
+
*/
|
|
845
|
+
turn?: {
|
|
846
|
+
seconds: number;
|
|
847
|
+
};
|
|
707
848
|
/** The card template: what every card in this box carries. */
|
|
708
849
|
fields: FieldDecl[];
|
|
709
850
|
properties: PropertyDecl[];
|
|
@@ -723,6 +864,30 @@ interface BundleContent {
|
|
|
723
864
|
interface BundleSettings {
|
|
724
865
|
playAdvancesTurns: number;
|
|
725
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* The play ladder (design/engine-server.md 4.10): how much of itself
|
|
869
|
+
* Storyletter shows this project, in one setting with three rungs rather than
|
|
870
|
+
* a set of toggles, because the features nest.
|
|
871
|
+
*
|
|
872
|
+
* solo one player, one flow: no sharing, no durability, no venue features
|
|
873
|
+
* shared several players over one world: sharing appears
|
|
874
|
+
* venue a production: nothing is hidden
|
|
875
|
+
*
|
|
876
|
+
* EDITOR-SIDE ONLY. It stays in the project shard beside `coverage` and
|
|
877
|
+
* `export` and is never compiled: a solo project plays on the same Engine as a
|
|
878
|
+
* venue one. Hidden is hidden rather than disabled, so going DOWN a rung is
|
|
879
|
+
* refused when the project already contains what the rung would hide, and a
|
|
880
|
+
* hand-edited shard above its rung is a compile warning.
|
|
881
|
+
*/
|
|
882
|
+
type PlayRung = "solo" | "shared" | "venue";
|
|
883
|
+
/** The default rung: a project shard that says nothing is a solo game. */
|
|
884
|
+
declare const DEFAULT_PLAY_RUNG: PlayRung;
|
|
885
|
+
/** The project shard's settings block: what the bundle carries, plus the
|
|
886
|
+
* authoring-side play rung that it does not. */
|
|
887
|
+
interface ProjectSettings extends BundleSettings {
|
|
888
|
+
/** The play ladder rung (see `PlayRung`). Absent = "solo". */
|
|
889
|
+
play?: PlayRung;
|
|
890
|
+
}
|
|
726
891
|
/**
|
|
727
892
|
* A map that a bundle was asked to carry: one spatial tag group's geometry,
|
|
728
893
|
* flattened for a host to draw (design/graphical-views.md 2, "The map MAY ship").
|
|
@@ -737,9 +902,16 @@ interface BundleSettings {
|
|
|
737
902
|
* names it passes to `peek`. There is nothing here to strip either, which is why
|
|
738
903
|
* `metadata: "stripped"` needs no special case: no titles, no purposes.
|
|
739
904
|
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
905
|
+
* SITES ARE HERE, which reverses a ruling. Until 2026-09-05 this comment said
|
|
906
|
+
* they were deliberately not: a site was where an author parked a hand while
|
|
907
|
+
* working, held in the view sidecar precisely because it was not content, and a
|
|
908
|
+
* host that wanted to place a hand had its zone from the compiled binding. That
|
|
909
|
+
* held for a game, where a hand's zone is its only real-world meaning. It does
|
|
910
|
+
* not hold for a physical experience (design/engine-server.md 4.3), where the
|
|
911
|
+
* position IS content: it is where the kiosk stands, and a producer's map is
|
|
912
|
+
* simply wrong without it. The alternative was a second file beside the bundle,
|
|
913
|
+
* which would cost a format the inspectors do not read and would put the view
|
|
914
|
+
* sidecar in the shipping path by the back door.
|
|
743
915
|
*/
|
|
744
916
|
interface BundleMap {
|
|
745
917
|
/** The owning box, by gameId (tag groups are box-scoped). */
|
|
@@ -755,6 +927,16 @@ interface BundleMap {
|
|
|
755
927
|
/** Background pictures, back to front, as bundle-relative paths. Hidden ones
|
|
756
928
|
* do not ship: what an author put away is not something to spring on a host. */
|
|
757
929
|
backgrounds?: BundleBackground[];
|
|
930
|
+
/** Where the placed hands stand on this map, by hand gameId, sorted by that
|
|
931
|
+
* gameId so the bytes do not depend on authoring order. A hand nobody has
|
|
932
|
+
* placed has no entry, and a map with no placed hand has no key at all. The
|
|
933
|
+
* zone a site sits in is NOT repeated here: the hand's own binding is what
|
|
934
|
+
* the runtime deals from, and a second copy could only go on to disagree. */
|
|
935
|
+
sites?: {
|
|
936
|
+
hand: string;
|
|
937
|
+
x: number;
|
|
938
|
+
y: number;
|
|
939
|
+
}[];
|
|
758
940
|
}
|
|
759
941
|
/** One shipped picture. `locked` and `hidden` are authoring state and do not
|
|
760
942
|
* travel; the draw order is the array order. */
|
|
@@ -844,6 +1026,75 @@ interface SaveEnvelope {
|
|
|
844
1026
|
shared: SharedSave;
|
|
845
1027
|
flows: Record<string, FlowSave>;
|
|
846
1028
|
}
|
|
1029
|
+
/** One card that a restore refused to put back on the board.
|
|
1030
|
+
*
|
|
1031
|
+
* `vanished` and `hand-vanished` are the edit's doing (the card, or the hand
|
|
1032
|
+
* it sat in, is no longer in the bundle). `claimed-elsewhere` is only ever a
|
|
1033
|
+
* single-flow restore into a LIVE engine: the card is shared, and the other
|
|
1034
|
+
* open flows already hold every copy the world has. */
|
|
1035
|
+
interface LoadEviction {
|
|
1036
|
+
flow: string;
|
|
1037
|
+
hand: string;
|
|
1038
|
+
card: string;
|
|
1039
|
+
reason: "vanished" | "hand-vanished" | "claimed-elsewhere";
|
|
1040
|
+
}
|
|
1041
|
+
/** One property the restore could not put back as it was. `flow` names the
|
|
1042
|
+
* flow whose half it belongs to; absent, it is the shared half.
|
|
1043
|
+
*
|
|
1044
|
+
* `path` is the engine's property address, spelled exactly as
|
|
1045
|
+
* `Flow.listProperties()` / `Engine.listProperties()` print it and exactly as
|
|
1046
|
+
* `getProperty` and `setProperty` accept it: `story.<name>` for the story
|
|
1047
|
+
* scope, `<scope>.<ownerGameId>.<name>` for the box, deck, hand and tag
|
|
1048
|
+
* scopes. No `@`, which belongs to the expression language and not to an
|
|
1049
|
+
* address.
|
|
1050
|
+
*
|
|
1051
|
+
* The owner segment is its GAMEID (design/engine-server.md 4.4), the name it
|
|
1052
|
+
* is called by everywhere else, so an operator reading a hot-swap report can
|
|
1053
|
+
* paste the address straight into `setProperty`. An owner the build no longer
|
|
1054
|
+
* has keeps the id the save carried: there is no gameId left to give it,
|
|
1055
|
+
* which is the rule the eviction list above has always used. */
|
|
1056
|
+
interface LoadProperty {
|
|
1057
|
+
flow?: string;
|
|
1058
|
+
path: string;
|
|
1059
|
+
}
|
|
1060
|
+
/** What a load or a flow restore would do that is not a plain restore
|
|
1061
|
+
* (design/engine-server.md 4.9). Arrays are sorted, so two runtimes given the
|
|
1062
|
+
* same save and bundle produce the same bytes; `flows` alone keeps the
|
|
1063
|
+
* envelope's own order, because a caller re-takes its handles in it. */
|
|
1064
|
+
interface LoadReport {
|
|
1065
|
+
/** No drift and nothing dropped, defaulted or retyped: the save goes back
|
|
1066
|
+
* exactly as it was. `flows` is not a divergence and does not count. */
|
|
1067
|
+
exact: boolean;
|
|
1068
|
+
project: string;
|
|
1069
|
+
/** Drift when the two differ; reported, never refused. */
|
|
1070
|
+
version: {
|
|
1071
|
+
saved: string;
|
|
1072
|
+
bundle: string;
|
|
1073
|
+
};
|
|
1074
|
+
/** Drift when the two differ; reported, never refused. */
|
|
1075
|
+
hash: {
|
|
1076
|
+
saved: string;
|
|
1077
|
+
bundle: string;
|
|
1078
|
+
};
|
|
1079
|
+
/** The flows this restores, in the order it restores them. */
|
|
1080
|
+
flows: string[];
|
|
1081
|
+
evicted: LoadEviction[];
|
|
1082
|
+
/** Cooldowns held for cards the bundle no longer has. */
|
|
1083
|
+
droppedCooldowns: {
|
|
1084
|
+
flow: string;
|
|
1085
|
+
card: string;
|
|
1086
|
+
}[];
|
|
1087
|
+
/** Shared `redraw: "never"` entries for cards the bundle no longer has. */
|
|
1088
|
+
droppedSpent: string[];
|
|
1089
|
+
/** In the save, not declared any more. */
|
|
1090
|
+
droppedProperties: LoadProperty[];
|
|
1091
|
+
/** Declared, not in the save: it takes the declaration's default. */
|
|
1092
|
+
defaultedProperties: LoadProperty[];
|
|
1093
|
+
/** In the save, still declared, but the saved value no longer fits the
|
|
1094
|
+
* declaration (its type changed, or an enum value / quality stage was
|
|
1095
|
+
* edited away). It takes the declaration's default. */
|
|
1096
|
+
retypedProperties: LoadProperty[];
|
|
1097
|
+
}
|
|
847
1098
|
/** The .storyletsave FILE: the HOST's file, not the engine's - the engine's
|
|
848
1099
|
* envelope plus, when the host keeps one, its @world container. This is
|
|
849
1100
|
* "host saves its container once, each engine saves its own envelope"
|
|
@@ -888,7 +1139,18 @@ declare const SHARD_EXTENSIONS: {
|
|
|
888
1139
|
* file and were retired: `purpose` already says why a thing exists, and
|
|
889
1140
|
* Patterpad's typed routing has no destination here. */
|
|
890
1141
|
readonly notes: ".storyletnotes";
|
|
1142
|
+
/** An installation contract: what a VENUE depends on, one file per
|
|
1143
|
+
* installation in `contracts/` at the project root
|
|
1144
|
+
* (design/engine-server.md 4.11). Its own shard, and its own folder, for the
|
|
1145
|
+
* walkthrough's reason (Reboot 7.5, S4): a different owner, a different
|
|
1146
|
+
* change rate, and a merge that must never collide with the author's edits,
|
|
1147
|
+
* since the server always wins its own file. */
|
|
1148
|
+
readonly contract: ".storyletcontract";
|
|
891
1149
|
};
|
|
1150
|
+
/** Where the installation contracts live, relative to the project root. The
|
|
1151
|
+
* directory is the registry, as it is for a box's decks: a contract exists
|
|
1152
|
+
* because its file exists. */
|
|
1153
|
+
declare const CONTRACTS_DIR = "contracts";
|
|
892
1154
|
declare const PROJECT_SCHEMA = "storylets/project@0";
|
|
893
1155
|
declare const BOX_SCHEMA = "storylets/box@0";
|
|
894
1156
|
declare const TAGS_SCHEMA = "storylets/tags@0";
|
|
@@ -898,6 +1160,68 @@ declare const VIEW_SCHEMA = "storylets/view@0";
|
|
|
898
1160
|
/** The comment sidecar's schema. Still called "notes" on disk: the file already
|
|
899
1161
|
* held both, and renaming it would break every project for no gain. */
|
|
900
1162
|
declare const NOTES_SCHEMA = "storylets/notes@0";
|
|
1163
|
+
declare const CONTRACT_SCHEMA = "storylets/contract@0";
|
|
1164
|
+
/**
|
|
1165
|
+
* What one installation depends on, written by the venue's server and read by
|
|
1166
|
+
* `validate` (design/engine-server.md 4.11).
|
|
1167
|
+
*
|
|
1168
|
+
* NOT THE AUTHOR'S FILE. A venue is provisioned against names - the hands its
|
|
1169
|
+
* stations deal, the boxes its scheduler ticks, the properties its clocks drive,
|
|
1170
|
+
* the fields its crew read - and the server writes them out so the tools that
|
|
1171
|
+
* already gate a build can refuse a rename before it reaches the venue. A
|
|
1172
|
+
* project playing at two venues has two of these. The author never edits one,
|
|
1173
|
+
* and today, with no server built, a project either receives one or has none.
|
|
1174
|
+
*
|
|
1175
|
+
* NEVER COMPILED. It is project-side config like `coverage` and `export`: the
|
|
1176
|
+
* server does not need its own contract handed back, it needs the bundle to
|
|
1177
|
+
* still honour it.
|
|
1178
|
+
*
|
|
1179
|
+
* BY GAMEID throughout, because a gameId is the name that crosses the project's
|
|
1180
|
+
* border and an internal id is authoring identity.
|
|
1181
|
+
*/
|
|
1182
|
+
interface ContractShard {
|
|
1183
|
+
schema: typeof CONTRACT_SCHEMA;
|
|
1184
|
+
/** The installation this contract speaks for. One file per installation, and
|
|
1185
|
+
* two files naming the same one is an error. */
|
|
1186
|
+
installation: string;
|
|
1187
|
+
/** Who wrote it, for a human reading the file ("Storylet Server 0.1.0"). */
|
|
1188
|
+
by?: string;
|
|
1189
|
+
/** The server's revision when it wrote this. */
|
|
1190
|
+
revision?: number;
|
|
1191
|
+
/** Hands a station is bound to, by gameId: they may not be renamed or
|
|
1192
|
+
* removed. */
|
|
1193
|
+
hands?: string[];
|
|
1194
|
+
/** Timed boxes the venue's scheduler ticks, by box gameId, with the turn unit
|
|
1195
|
+
* in SECONDS it was provisioned against. A box whose unit changed means every
|
|
1196
|
+
* rest on its cards changed meaning. */
|
|
1197
|
+
boxes?: Record<string, {
|
|
1198
|
+
turn: number;
|
|
1199
|
+
}>;
|
|
1200
|
+
/** Property paths the venue reads or drives, in the engine's own address
|
|
1201
|
+
* grammar with no `@` ("world.time_wall", "story.visits"), which is how
|
|
1202
|
+
* `listProperties()` prints them. */
|
|
1203
|
+
properties?: ContractProperty[];
|
|
1204
|
+
/** Card-template field names the crew and the bridges read. */
|
|
1205
|
+
fields?: string[];
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* One contracted property.
|
|
1209
|
+
*
|
|
1210
|
+
* A bare path is the common form and the one the spec's example writes. The
|
|
1211
|
+
* object form adds the TYPE the venue was provisioned against, which is the only
|
|
1212
|
+
* way `validate` can catch the break that costs a producer most: a property that
|
|
1213
|
+
* still exists under the same name and now holds something else. A server that
|
|
1214
|
+
* knows the type should write the object form; a hand-written contract may say
|
|
1215
|
+
* only the path and get the existence check alone.
|
|
1216
|
+
*/
|
|
1217
|
+
type ContractProperty = string | {
|
|
1218
|
+
path: string;
|
|
1219
|
+
type?: PropertyType;
|
|
1220
|
+
};
|
|
1221
|
+
/** The path a contracted property names, whichever form it was written in. */
|
|
1222
|
+
declare const contractPropertyPath: (p: ContractProperty) => string;
|
|
1223
|
+
/** The type a contracted property was provisioned against, when it says. */
|
|
1224
|
+
declare const contractPropertyType: (p: ContractProperty) => PropertyType | undefined;
|
|
901
1225
|
/** A point in a canvas's own coordinates. */
|
|
902
1226
|
interface ViewPoint {
|
|
903
1227
|
x: number;
|
|
@@ -1017,7 +1341,7 @@ interface ProjectShard {
|
|
|
1017
1341
|
name: string;
|
|
1018
1342
|
version: string;
|
|
1019
1343
|
};
|
|
1020
|
-
settings:
|
|
1344
|
+
settings: ProjectSettings;
|
|
1021
1345
|
/** Coverage drivers + argument domains (authoring/testing config; stays
|
|
1022
1346
|
* out of the compiled bundle). */
|
|
1023
1347
|
coverage?: CoverageConfig;
|
|
@@ -1087,6 +1411,10 @@ interface BoxShard {
|
|
|
1087
1411
|
ranking: {
|
|
1088
1412
|
specificity: boolean;
|
|
1089
1413
|
};
|
|
1414
|
+
/** Declares a timed box (see `Box.turn`); compiled through unchanged. */
|
|
1415
|
+
turn?: {
|
|
1416
|
+
seconds: number;
|
|
1417
|
+
};
|
|
1090
1418
|
fields: FieldDecl[];
|
|
1091
1419
|
properties: PropertyDecl[];
|
|
1092
1420
|
};
|
|
@@ -1112,6 +1440,8 @@ interface DeckShard {
|
|
|
1112
1440
|
condition?: string;
|
|
1113
1441
|
/** Scarce across flows: see Deck.shared. */
|
|
1114
1442
|
shared?: boolean;
|
|
1443
|
+
/** Its `redraw: "never"` cards are spent past the run: see Deck.durable. */
|
|
1444
|
+
durable?: boolean;
|
|
1115
1445
|
/** Authored display order within the box (sparse; see BoxShard). */
|
|
1116
1446
|
order?: number;
|
|
1117
1447
|
properties: PropertyDecl[];
|
|
@@ -1119,4 +1449,4 @@ interface DeckShard {
|
|
|
1119
1449
|
cards: Card<string>[];
|
|
1120
1450
|
}
|
|
1121
1451
|
|
|
1122
|
-
export { BOX_SCHEMA, BUNDLE_EXTENSION, BUNDLE_SCHEMA, type Box, type BoxMap, type BoxShard, type Bundle, type BundleBackground, type BundleContent, type BundleMap, type BundleSettings, type CanvasFurniture, type Card, type Comment, type CommentMark, type CommentMessage, type CoverageConfig, type CoverageDriver, DECK_SCHEMA, type Deck, type DeckCanvas, type DeckShard, FURNITURE_COLOURS, type FieldDecl, type FlowSave, type Frame, type FurnitureColour, HANDS_SCHEMA, type Hand, type HandBinding, type HandRule, type HandTemplate, type HandsShard, NOTES_SCHEMA, type NotesShard, type Outcome, PLACE_GROUP, PROJECT_FOLDER_EXTENSION, PROJECT_SCHEMA, type PlayRecord, type Polygon, type ProjectShard, type PropertyBag, type PropertyDecl, type PropertyType, type PropsPartition, type Rect, type RedrawPolicy, SAVEFILE_SCHEMA, SAVE_SCHEMA, SHARD_EXTENSIONS, SPATIAL, type SaveEnvelope, type SaveFile, type SharedSave, type SpatialBackground, type SpatialGroup, type StackMove, type Stacked, TAGS_SCHEMA, type Tag, type TagGroup, type TagsShard, VIEW_SCHEMA, type ViewPoint, type ViewShard, backgroundsOf, bindHand, bundleAssetPath, byDisplayOrder, centroid, commentsOf, droppedRect, effectiveGameId, framesOf, freeGameId, freeTitle, gameIdify, handBinding, inferDeclFromWrite, isSpatial, isValidGameId, labelPoint, markOf, marksOn, openThreadCounts, pointInPolygon, polygonBounds, polygonOf, restack, spatialOf, stacked, threadsFor, unbindHand, withBackgrounds, withPolygon, withSpatialGroup, withZ, zOf, zoneAt, zonesAt };
|
|
1452
|
+
export { BOX_SCHEMA, BUNDLE_EXTENSION, BUNDLE_SCHEMA, type Box, type BoxMap, type BoxShard, type Bundle, type BundleBackground, type BundleContent, type BundleMap, type BundleSettings, CONTRACTS_DIR, CONTRACT_SCHEMA, type CanvasFurniture, type Card, type Comment, type CommentMark, type CommentMessage, type ContractProperty, type ContractShard, type CoverageConfig, type CoverageDriver, DECK_SCHEMA, DEFAULT_PLAY_RUNG, type Deck, type DeckCanvas, type DeckShard, FURNITURE_COLOURS, type FieldDecl, type FlowSave, type Frame, type FurnitureColour, HANDS_SCHEMA, type Hand, type HandBinding, type HandRule, type HandTemplate, type HandsShard, type HoleRef, type HoleRefScope, type LoadEviction, type LoadProperty, type LoadReport, NOTES_SCHEMA, type NotesShard, type Outcome, PLACE_GROUP, PROJECT_FOLDER_EXTENSION, PROJECT_SCHEMA, type PlayRecord, type PlayRung, type Polygon, type ProjectSettings, type ProjectShard, type PropertyBag, type PropertyDecl, type PropertyType, type PropsPartition, type Rect, type RedrawPolicy, SAVEFILE_SCHEMA, SAVE_SCHEMA, SHARD_EXTENSIONS, SPATIAL, type SaveEnvelope, type SaveFile, type SharedSave, type SpatialBackground, type SpatialGroup, type StackMove, type Stacked, TAGS_SCHEMA, type Tag, type TagGroup, type TagsShard, VIEW_SCHEMA, type ValueAddresses, type ViewPoint, type ViewShard, ambiguousValueAddressMessage, backgroundsOf, bindHand, bundleAssetPath, byDisplayOrder, centroid, commentsOf, contractPropertyPath, contractPropertyType, droppedRect, effectiveGameId, framesOf, freeGameId, freeTitle, gameIdify, handBinding, inferDeclFromWrite, isHoleRef, isSpatial, isValidGameId, labelPoint, markOf, marksOn, openThreadCounts, parseHoleRef, pointInPolygon, polygonBounds, polygonOf, restack, spatialOf, stacked, threadsFor, turnSpan, unbindHand, valueAddresses, withBackgrounds, withPolygon, withSpatialGroup, withZ, zOf, zoneAt, zonesAt };
|
package/dist/index.d.ts
CHANGED
|
@@ -409,6 +409,25 @@ interface PropertyDecl {
|
|
|
409
409
|
* the game's own state, always engine-level, never per-flow.
|
|
410
410
|
*/
|
|
411
411
|
shared?: boolean;
|
|
412
|
+
/**
|
|
413
|
+
* The durability axis (design/engine-server.md 4.2), valid wherever `shared`
|
|
414
|
+
* is valid and orthogonal to it: `shared` says whose value this is WITHIN a
|
|
415
|
+
* run, `durable` says whether the value survives the run at all. A durable
|
|
416
|
+
* shared property is the installation's memory ("trolls defeated since we
|
|
417
|
+
* opened"); a durable per-flow one is the player's pocket (visits,
|
|
418
|
+
* allegiance, what they earned).
|
|
419
|
+
*
|
|
420
|
+
* INERT TO THE RUNTIME. The engine partitions by `shared` alone and never
|
|
421
|
+
* reads this. Durability is what the SERVER does at a run boundary: it reads
|
|
422
|
+
* the declarations, lifts the durable values out of the partitions before the
|
|
423
|
+
* world restarts, and writes them back into the fresh engine afterwards,
|
|
424
|
+
* entirely through `getProperty` / `setProperty`.
|
|
425
|
+
*
|
|
426
|
+
* On a `@world` declaration the flag is a validation error, for the reason
|
|
427
|
+
* `shared` is: @world is the game's own state, and how long the game keeps it
|
|
428
|
+
* is the game's business.
|
|
429
|
+
*/
|
|
430
|
+
durable?: boolean;
|
|
412
431
|
purpose?: string;
|
|
413
432
|
}
|
|
414
433
|
/** A card-template field (box-defined). Data for the host; the engine never
|
|
@@ -433,6 +452,32 @@ declare function effectiveGameId(entity: {
|
|
|
433
452
|
title?: string;
|
|
434
453
|
id: string;
|
|
435
454
|
}): string;
|
|
455
|
+
/** The three answers a value address needs, all derived from the bundle. */
|
|
456
|
+
interface ValueAddresses {
|
|
457
|
+
/** Tag internal id -> the owner segment an address PRINTS for it. */
|
|
458
|
+
print: Map<string, string>;
|
|
459
|
+
/** Every owner segment a value address ACCEPTS -> the tag's internal id.
|
|
460
|
+
* Holds the qualified form for every tag and the short form only for a
|
|
461
|
+
* gameId no other tag shares. */
|
|
462
|
+
accept: Map<string, string>;
|
|
463
|
+
/** A tag gameId more than one box uses -> its qualified forms, in bundle
|
|
464
|
+
* order. Empty for the overwhelming majority of projects, and what a
|
|
465
|
+
* refusal lists. */
|
|
466
|
+
repeated: Map<string, string[]>;
|
|
467
|
+
}
|
|
468
|
+
/** The owner segment of every tag in the bundle, both ways round. */
|
|
469
|
+
declare function valueAddresses(bundle: {
|
|
470
|
+
boxes: readonly {
|
|
471
|
+
id: string;
|
|
472
|
+
gameId?: string;
|
|
473
|
+
title?: string;
|
|
474
|
+
tagGroups: readonly TagGroup[];
|
|
475
|
+
}[];
|
|
476
|
+
}): ValueAddresses;
|
|
477
|
+
/** What an ambiguous short-form value address is told: the candidates, in
|
|
478
|
+
* full, because "that names two tags" without them leaves a host reading a
|
|
479
|
+
* bundle it did not write to find out which boxes. */
|
|
480
|
+
declare function ambiguousValueAddressMessage(segment: string, name: string, candidates: readonly string[]): string;
|
|
436
481
|
/**
|
|
437
482
|
* The first free gameId of the form `base`, `base-2`, `base-3`, ... not already
|
|
438
483
|
* in `taken`.
|
|
@@ -473,6 +518,17 @@ declare function byDisplayOrder<T extends {
|
|
|
473
518
|
order?: number;
|
|
474
519
|
}>(items: readonly T[]): T[];
|
|
475
520
|
declare function freeTitle(base: string, taken: ReadonlySet<string>): string;
|
|
521
|
+
/**
|
|
522
|
+
* A count of a TIMED box's turns, said as time (design/engine-server.md 4.8):
|
|
523
|
+
* `turnSpan(30, 60)` is "30 min", and `turnSpan(30, 60, true)` is "30 minutes".
|
|
524
|
+
*
|
|
525
|
+
* One definition, because the conversion appears wherever a designer might
|
|
526
|
+
* otherwise have to do it in their head: the card editor's Redraw field, the
|
|
527
|
+
* box page, the Board's advance buttons, and the coverage report's turn
|
|
528
|
+
* budget. Two of those want the unit spelled out and two want it short, which
|
|
529
|
+
* is the whole of `long`.
|
|
530
|
+
*/
|
|
531
|
+
declare function turnSpan(turns: number, seconds: number, long?: boolean): string;
|
|
476
532
|
interface Outcome<E> {
|
|
477
533
|
id: string;
|
|
478
534
|
gameId?: string;
|
|
@@ -528,6 +584,21 @@ interface Card<E> {
|
|
|
528
584
|
* common case writes one number and "five in the world, one to a customer"
|
|
529
585
|
* is `copies: 1, sharedCopies: 5`. */
|
|
530
586
|
sharedCopies?: number;
|
|
587
|
+
/** Does this card's `redraw: "never"` spend survive the run
|
|
588
|
+
* (design/engine-server.md 4.2)? Absent takes the deck's flag, set here it
|
|
589
|
+
* overrides the deck, exactly as `shared` does. `shared` decides who a
|
|
590
|
+
* spend counts for WITHIN a run; this decides whether it outlives one.
|
|
591
|
+
*
|
|
592
|
+
* Only `"never"` crosses the run boundary, for the reason only `"never"`
|
|
593
|
+
* crosses the flow boundary (shared-scarcity 9.3.2): a finite cooldown is
|
|
594
|
+
* an absolute turn of a box clock, and the clock resets with the run. On
|
|
595
|
+
* any other redraw the flag is a compile warning.
|
|
596
|
+
*
|
|
597
|
+
* INERT TO THE RUNTIME, like the declaration flag: the server lifts the
|
|
598
|
+
* durable spends at run end (per-flow ones from the flow's `never`
|
|
599
|
+
* cooldowns, shared ones from the engine's spent set) and puts them back
|
|
600
|
+
* through `openFlow(id, { restore })` and `markTaken`. */
|
|
601
|
+
durable?: boolean;
|
|
531
602
|
/** Card-template data: field name -> value, validated at publish. */
|
|
532
603
|
fields?: Record<string, ScalarValue>;
|
|
533
604
|
outcomes: Outcome<E>[];
|
|
@@ -543,6 +614,11 @@ interface Deck<E> {
|
|
|
543
614
|
* in it is shared unless the card says otherwise. The container is where
|
|
544
615
|
* Patter puts its own shared-memory flag, and the deck is our container. */
|
|
545
616
|
shared?: boolean;
|
|
617
|
+
/** Every `redraw: "never"` card in this pile is spent for good, past the end
|
|
618
|
+
* of the run, unless the card says otherwise (design/engine-server.md 4.2).
|
|
619
|
+
* The container carries the flag for the reason `shared` is carried here:
|
|
620
|
+
* a pile is what an author reaches for when a rule is true of all of it. */
|
|
621
|
+
durable?: boolean;
|
|
546
622
|
properties: PropertyDecl[];
|
|
547
623
|
cards: Card<E>[];
|
|
548
624
|
}
|
|
@@ -638,6 +714,30 @@ interface TagGroup {
|
|
|
638
714
|
* half of that answer. `home` was a metaphor an author had to learn, and it
|
|
639
715
|
* leaked into hand-edited shards and the docs. */
|
|
640
716
|
declare const PLACE_GROUP = "place";
|
|
717
|
+
/** The scopes a movable hole may be filled from (design/engine-server.md 4.6):
|
|
718
|
+
* the two `boundBy` already allows, plus `@hand` - the asking hand's OWN
|
|
719
|
+
* declared property, resolved before tag composition so a movable hole can
|
|
720
|
+
* never depend on the tags it is choosing. */
|
|
721
|
+
type HoleRefScope = "hand" | "story" | "world";
|
|
722
|
+
/** A parsed hole reference: `@hand.zone` -> `{ scope: "hand", name: "zone" }`. */
|
|
723
|
+
interface HoleRef {
|
|
724
|
+
scope: HoleRefScope;
|
|
725
|
+
name: string;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Is this `chosen` / binding value MEANT as a property reference rather than a
|
|
729
|
+
* tag id?
|
|
730
|
+
*
|
|
731
|
+
* The test is the leading `@` alone, deliberately: a value that starts with
|
|
732
|
+
* one and does not parse is a mistyped reference, which the compiler should
|
|
733
|
+
* name as such, not a tag id that happens to look odd. Tag ids never begin
|
|
734
|
+
* with `@`.
|
|
735
|
+
*/
|
|
736
|
+
declare const isHoleRef: (value: string) => boolean;
|
|
737
|
+
/** Parse a hole reference, or undefined when it is not one. The on-disk form
|
|
738
|
+
* stays a plain string, so the canonical serialiser and the shard merge need
|
|
739
|
+
* no change at all: a hole is still one group name against one value. */
|
|
740
|
+
declare const parseHoleRef: (value: string) => HoleRef | undefined;
|
|
641
741
|
/** A declared kind of hand (schema 2.6): live-inherited, author-side only,
|
|
642
742
|
* never called from game code. One condition governs every instance. */
|
|
643
743
|
interface HandTemplate<E> {
|
|
@@ -649,9 +749,12 @@ interface HandTemplate<E> {
|
|
|
649
749
|
* position). Merges as a per-item value, so id-sorted storage stays
|
|
650
750
|
* merge-clean (Reboot 7.4). */
|
|
651
751
|
order?: number;
|
|
652
|
-
/** Fixed tag bindings: tag group id -> tag id.
|
|
752
|
+
/** Fixed tag bindings: tag group id -> tag id. Literal tags only: what a
|
|
753
|
+
* template FIXES is the same for every instance, and a hole that moves is
|
|
754
|
+
* the instance's own business (`Hand.chosen`, 4.6). */
|
|
653
755
|
bindings?: Record<string, string>;
|
|
654
|
-
/** The holes: tag group ids each instance fills (one tag each
|
|
756
|
+
/** The holes: tag group ids each instance fills (one tag each, or one
|
|
757
|
+
* property reference: 4.6). */
|
|
655
758
|
chooses?: string[];
|
|
656
759
|
/** Shared availability condition, ANDed in (schema 3.1); evaluated per
|
|
657
760
|
* instance against that instance's composed @hand. */
|
|
@@ -663,6 +766,15 @@ interface HandTemplate<E> {
|
|
|
663
766
|
}
|
|
664
767
|
/** A standalone hand's inline rule (schema 2.6): owned by the hand. */
|
|
665
768
|
interface HandRule<E> {
|
|
769
|
+
/**
|
|
770
|
+
* Tag group id -> tag id, or a PROPERTY REFERENCE (`"@hand.zone"`,
|
|
771
|
+
* `"@story.where"`, `"@world.place"`) the runtime resolves at ask time
|
|
772
|
+
* (design/engine-server.md 4.6, the hand that moves). Still a plain string
|
|
773
|
+
* on disk, so the canonical serialiser and the merge are untouched; what
|
|
774
|
+
* widened is the meaning, and `parseHoleRef` is where it is read.
|
|
775
|
+
*
|
|
776
|
+
* `place` is never fillable this way: it is the hand's own name, not an axis.
|
|
777
|
+
*/
|
|
666
778
|
bindings?: Record<string, string>;
|
|
667
779
|
condition?: E;
|
|
668
780
|
slots: number | "unbounded";
|
|
@@ -679,7 +791,17 @@ interface Hand<E> {
|
|
|
679
791
|
purpose?: string;
|
|
680
792
|
/** Hand template id (not gameId). */
|
|
681
793
|
template?: string;
|
|
682
|
-
/**
|
|
794
|
+
/**
|
|
795
|
+
* Template instances: tag group id -> tag id, one per `chooses` hole.
|
|
796
|
+
*
|
|
797
|
+
* A value may instead be a PROPERTY REFERENCE (`"@hand.zone"`,
|
|
798
|
+
* `"@story.where"`, `"@world.place"`), which makes the hole MOVABLE: the
|
|
799
|
+
* runtime resolves the reference at ask time and binds the hole to the tag
|
|
800
|
+
* the value names, so moving the Elder to the forest is `setProperty` and
|
|
801
|
+
* nothing else (design/engine-server.md 4.6). Still a plain string on disk,
|
|
802
|
+
* so the canonical serialiser and the shard merge need no change; read it
|
|
803
|
+
* with `parseHoleRef`.
|
|
804
|
+
*/
|
|
683
805
|
chosen?: Record<string, string>;
|
|
684
806
|
/** Standalone hands: the inline rule. */
|
|
685
807
|
rule?: HandRule<E>;
|
|
@@ -704,6 +826,25 @@ interface Box<E> {
|
|
|
704
826
|
ranking: {
|
|
705
827
|
specificity: boolean;
|
|
706
828
|
};
|
|
829
|
+
/**
|
|
830
|
+
* A TIMED box: its clock counts real time, one turn every `seconds` of the
|
|
831
|
+
* run (design/engine-server.md 4.8). Absent is the ordinary box, whose turn
|
|
832
|
+
* is a play.
|
|
833
|
+
*
|
|
834
|
+
* Two things follow, and only two. In the ENGINE, a play in this box
|
|
835
|
+
* defaults to advancing nothing: `settings.playAdvancesTurns` does not
|
|
836
|
+
* apply, so a designer cannot declare the convention and then forget to
|
|
837
|
+
* switch play-advance off. Everywhere else it is what the tools SAY: the
|
|
838
|
+
* host ticks the box (the runtime has no clock and gains none here), and a
|
|
839
|
+
* card's `redraw: N` reads as N x `seconds`, which the editors, the bundle
|
|
840
|
+
* inspectors and the coverage report spell out rather than leaving a
|
|
841
|
+
* designer to know that 30 meant minutes.
|
|
842
|
+
*
|
|
843
|
+
* The number itself is inert to the runtime, which never reads it.
|
|
844
|
+
*/
|
|
845
|
+
turn?: {
|
|
846
|
+
seconds: number;
|
|
847
|
+
};
|
|
707
848
|
/** The card template: what every card in this box carries. */
|
|
708
849
|
fields: FieldDecl[];
|
|
709
850
|
properties: PropertyDecl[];
|
|
@@ -723,6 +864,30 @@ interface BundleContent {
|
|
|
723
864
|
interface BundleSettings {
|
|
724
865
|
playAdvancesTurns: number;
|
|
725
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* The play ladder (design/engine-server.md 4.10): how much of itself
|
|
869
|
+
* Storyletter shows this project, in one setting with three rungs rather than
|
|
870
|
+
* a set of toggles, because the features nest.
|
|
871
|
+
*
|
|
872
|
+
* solo one player, one flow: no sharing, no durability, no venue features
|
|
873
|
+
* shared several players over one world: sharing appears
|
|
874
|
+
* venue a production: nothing is hidden
|
|
875
|
+
*
|
|
876
|
+
* EDITOR-SIDE ONLY. It stays in the project shard beside `coverage` and
|
|
877
|
+
* `export` and is never compiled: a solo project plays on the same Engine as a
|
|
878
|
+
* venue one. Hidden is hidden rather than disabled, so going DOWN a rung is
|
|
879
|
+
* refused when the project already contains what the rung would hide, and a
|
|
880
|
+
* hand-edited shard above its rung is a compile warning.
|
|
881
|
+
*/
|
|
882
|
+
type PlayRung = "solo" | "shared" | "venue";
|
|
883
|
+
/** The default rung: a project shard that says nothing is a solo game. */
|
|
884
|
+
declare const DEFAULT_PLAY_RUNG: PlayRung;
|
|
885
|
+
/** The project shard's settings block: what the bundle carries, plus the
|
|
886
|
+
* authoring-side play rung that it does not. */
|
|
887
|
+
interface ProjectSettings extends BundleSettings {
|
|
888
|
+
/** The play ladder rung (see `PlayRung`). Absent = "solo". */
|
|
889
|
+
play?: PlayRung;
|
|
890
|
+
}
|
|
726
891
|
/**
|
|
727
892
|
* A map that a bundle was asked to carry: one spatial tag group's geometry,
|
|
728
893
|
* flattened for a host to draw (design/graphical-views.md 2, "The map MAY ship").
|
|
@@ -737,9 +902,16 @@ interface BundleSettings {
|
|
|
737
902
|
* names it passes to `peek`. There is nothing here to strip either, which is why
|
|
738
903
|
* `metadata: "stripped"` needs no special case: no titles, no purposes.
|
|
739
904
|
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
905
|
+
* SITES ARE HERE, which reverses a ruling. Until 2026-09-05 this comment said
|
|
906
|
+
* they were deliberately not: a site was where an author parked a hand while
|
|
907
|
+
* working, held in the view sidecar precisely because it was not content, and a
|
|
908
|
+
* host that wanted to place a hand had its zone from the compiled binding. That
|
|
909
|
+
* held for a game, where a hand's zone is its only real-world meaning. It does
|
|
910
|
+
* not hold for a physical experience (design/engine-server.md 4.3), where the
|
|
911
|
+
* position IS content: it is where the kiosk stands, and a producer's map is
|
|
912
|
+
* simply wrong without it. The alternative was a second file beside the bundle,
|
|
913
|
+
* which would cost a format the inspectors do not read and would put the view
|
|
914
|
+
* sidecar in the shipping path by the back door.
|
|
743
915
|
*/
|
|
744
916
|
interface BundleMap {
|
|
745
917
|
/** The owning box, by gameId (tag groups are box-scoped). */
|
|
@@ -755,6 +927,16 @@ interface BundleMap {
|
|
|
755
927
|
/** Background pictures, back to front, as bundle-relative paths. Hidden ones
|
|
756
928
|
* do not ship: what an author put away is not something to spring on a host. */
|
|
757
929
|
backgrounds?: BundleBackground[];
|
|
930
|
+
/** Where the placed hands stand on this map, by hand gameId, sorted by that
|
|
931
|
+
* gameId so the bytes do not depend on authoring order. A hand nobody has
|
|
932
|
+
* placed has no entry, and a map with no placed hand has no key at all. The
|
|
933
|
+
* zone a site sits in is NOT repeated here: the hand's own binding is what
|
|
934
|
+
* the runtime deals from, and a second copy could only go on to disagree. */
|
|
935
|
+
sites?: {
|
|
936
|
+
hand: string;
|
|
937
|
+
x: number;
|
|
938
|
+
y: number;
|
|
939
|
+
}[];
|
|
758
940
|
}
|
|
759
941
|
/** One shipped picture. `locked` and `hidden` are authoring state and do not
|
|
760
942
|
* travel; the draw order is the array order. */
|
|
@@ -844,6 +1026,75 @@ interface SaveEnvelope {
|
|
|
844
1026
|
shared: SharedSave;
|
|
845
1027
|
flows: Record<string, FlowSave>;
|
|
846
1028
|
}
|
|
1029
|
+
/** One card that a restore refused to put back on the board.
|
|
1030
|
+
*
|
|
1031
|
+
* `vanished` and `hand-vanished` are the edit's doing (the card, or the hand
|
|
1032
|
+
* it sat in, is no longer in the bundle). `claimed-elsewhere` is only ever a
|
|
1033
|
+
* single-flow restore into a LIVE engine: the card is shared, and the other
|
|
1034
|
+
* open flows already hold every copy the world has. */
|
|
1035
|
+
interface LoadEviction {
|
|
1036
|
+
flow: string;
|
|
1037
|
+
hand: string;
|
|
1038
|
+
card: string;
|
|
1039
|
+
reason: "vanished" | "hand-vanished" | "claimed-elsewhere";
|
|
1040
|
+
}
|
|
1041
|
+
/** One property the restore could not put back as it was. `flow` names the
|
|
1042
|
+
* flow whose half it belongs to; absent, it is the shared half.
|
|
1043
|
+
*
|
|
1044
|
+
* `path` is the engine's property address, spelled exactly as
|
|
1045
|
+
* `Flow.listProperties()` / `Engine.listProperties()` print it and exactly as
|
|
1046
|
+
* `getProperty` and `setProperty` accept it: `story.<name>` for the story
|
|
1047
|
+
* scope, `<scope>.<ownerGameId>.<name>` for the box, deck, hand and tag
|
|
1048
|
+
* scopes. No `@`, which belongs to the expression language and not to an
|
|
1049
|
+
* address.
|
|
1050
|
+
*
|
|
1051
|
+
* The owner segment is its GAMEID (design/engine-server.md 4.4), the name it
|
|
1052
|
+
* is called by everywhere else, so an operator reading a hot-swap report can
|
|
1053
|
+
* paste the address straight into `setProperty`. An owner the build no longer
|
|
1054
|
+
* has keeps the id the save carried: there is no gameId left to give it,
|
|
1055
|
+
* which is the rule the eviction list above has always used. */
|
|
1056
|
+
interface LoadProperty {
|
|
1057
|
+
flow?: string;
|
|
1058
|
+
path: string;
|
|
1059
|
+
}
|
|
1060
|
+
/** What a load or a flow restore would do that is not a plain restore
|
|
1061
|
+
* (design/engine-server.md 4.9). Arrays are sorted, so two runtimes given the
|
|
1062
|
+
* same save and bundle produce the same bytes; `flows` alone keeps the
|
|
1063
|
+
* envelope's own order, because a caller re-takes its handles in it. */
|
|
1064
|
+
interface LoadReport {
|
|
1065
|
+
/** No drift and nothing dropped, defaulted or retyped: the save goes back
|
|
1066
|
+
* exactly as it was. `flows` is not a divergence and does not count. */
|
|
1067
|
+
exact: boolean;
|
|
1068
|
+
project: string;
|
|
1069
|
+
/** Drift when the two differ; reported, never refused. */
|
|
1070
|
+
version: {
|
|
1071
|
+
saved: string;
|
|
1072
|
+
bundle: string;
|
|
1073
|
+
};
|
|
1074
|
+
/** Drift when the two differ; reported, never refused. */
|
|
1075
|
+
hash: {
|
|
1076
|
+
saved: string;
|
|
1077
|
+
bundle: string;
|
|
1078
|
+
};
|
|
1079
|
+
/** The flows this restores, in the order it restores them. */
|
|
1080
|
+
flows: string[];
|
|
1081
|
+
evicted: LoadEviction[];
|
|
1082
|
+
/** Cooldowns held for cards the bundle no longer has. */
|
|
1083
|
+
droppedCooldowns: {
|
|
1084
|
+
flow: string;
|
|
1085
|
+
card: string;
|
|
1086
|
+
}[];
|
|
1087
|
+
/** Shared `redraw: "never"` entries for cards the bundle no longer has. */
|
|
1088
|
+
droppedSpent: string[];
|
|
1089
|
+
/** In the save, not declared any more. */
|
|
1090
|
+
droppedProperties: LoadProperty[];
|
|
1091
|
+
/** Declared, not in the save: it takes the declaration's default. */
|
|
1092
|
+
defaultedProperties: LoadProperty[];
|
|
1093
|
+
/** In the save, still declared, but the saved value no longer fits the
|
|
1094
|
+
* declaration (its type changed, or an enum value / quality stage was
|
|
1095
|
+
* edited away). It takes the declaration's default. */
|
|
1096
|
+
retypedProperties: LoadProperty[];
|
|
1097
|
+
}
|
|
847
1098
|
/** The .storyletsave FILE: the HOST's file, not the engine's - the engine's
|
|
848
1099
|
* envelope plus, when the host keeps one, its @world container. This is
|
|
849
1100
|
* "host saves its container once, each engine saves its own envelope"
|
|
@@ -888,7 +1139,18 @@ declare const SHARD_EXTENSIONS: {
|
|
|
888
1139
|
* file and were retired: `purpose` already says why a thing exists, and
|
|
889
1140
|
* Patterpad's typed routing has no destination here. */
|
|
890
1141
|
readonly notes: ".storyletnotes";
|
|
1142
|
+
/** An installation contract: what a VENUE depends on, one file per
|
|
1143
|
+
* installation in `contracts/` at the project root
|
|
1144
|
+
* (design/engine-server.md 4.11). Its own shard, and its own folder, for the
|
|
1145
|
+
* walkthrough's reason (Reboot 7.5, S4): a different owner, a different
|
|
1146
|
+
* change rate, and a merge that must never collide with the author's edits,
|
|
1147
|
+
* since the server always wins its own file. */
|
|
1148
|
+
readonly contract: ".storyletcontract";
|
|
891
1149
|
};
|
|
1150
|
+
/** Where the installation contracts live, relative to the project root. The
|
|
1151
|
+
* directory is the registry, as it is for a box's decks: a contract exists
|
|
1152
|
+
* because its file exists. */
|
|
1153
|
+
declare const CONTRACTS_DIR = "contracts";
|
|
892
1154
|
declare const PROJECT_SCHEMA = "storylets/project@0";
|
|
893
1155
|
declare const BOX_SCHEMA = "storylets/box@0";
|
|
894
1156
|
declare const TAGS_SCHEMA = "storylets/tags@0";
|
|
@@ -898,6 +1160,68 @@ declare const VIEW_SCHEMA = "storylets/view@0";
|
|
|
898
1160
|
/** The comment sidecar's schema. Still called "notes" on disk: the file already
|
|
899
1161
|
* held both, and renaming it would break every project for no gain. */
|
|
900
1162
|
declare const NOTES_SCHEMA = "storylets/notes@0";
|
|
1163
|
+
declare const CONTRACT_SCHEMA = "storylets/contract@0";
|
|
1164
|
+
/**
|
|
1165
|
+
* What one installation depends on, written by the venue's server and read by
|
|
1166
|
+
* `validate` (design/engine-server.md 4.11).
|
|
1167
|
+
*
|
|
1168
|
+
* NOT THE AUTHOR'S FILE. A venue is provisioned against names - the hands its
|
|
1169
|
+
* stations deal, the boxes its scheduler ticks, the properties its clocks drive,
|
|
1170
|
+
* the fields its crew read - and the server writes them out so the tools that
|
|
1171
|
+
* already gate a build can refuse a rename before it reaches the venue. A
|
|
1172
|
+
* project playing at two venues has two of these. The author never edits one,
|
|
1173
|
+
* and today, with no server built, a project either receives one or has none.
|
|
1174
|
+
*
|
|
1175
|
+
* NEVER COMPILED. It is project-side config like `coverage` and `export`: the
|
|
1176
|
+
* server does not need its own contract handed back, it needs the bundle to
|
|
1177
|
+
* still honour it.
|
|
1178
|
+
*
|
|
1179
|
+
* BY GAMEID throughout, because a gameId is the name that crosses the project's
|
|
1180
|
+
* border and an internal id is authoring identity.
|
|
1181
|
+
*/
|
|
1182
|
+
interface ContractShard {
|
|
1183
|
+
schema: typeof CONTRACT_SCHEMA;
|
|
1184
|
+
/** The installation this contract speaks for. One file per installation, and
|
|
1185
|
+
* two files naming the same one is an error. */
|
|
1186
|
+
installation: string;
|
|
1187
|
+
/** Who wrote it, for a human reading the file ("Storylet Server 0.1.0"). */
|
|
1188
|
+
by?: string;
|
|
1189
|
+
/** The server's revision when it wrote this. */
|
|
1190
|
+
revision?: number;
|
|
1191
|
+
/** Hands a station is bound to, by gameId: they may not be renamed or
|
|
1192
|
+
* removed. */
|
|
1193
|
+
hands?: string[];
|
|
1194
|
+
/** Timed boxes the venue's scheduler ticks, by box gameId, with the turn unit
|
|
1195
|
+
* in SECONDS it was provisioned against. A box whose unit changed means every
|
|
1196
|
+
* rest on its cards changed meaning. */
|
|
1197
|
+
boxes?: Record<string, {
|
|
1198
|
+
turn: number;
|
|
1199
|
+
}>;
|
|
1200
|
+
/** Property paths the venue reads or drives, in the engine's own address
|
|
1201
|
+
* grammar with no `@` ("world.time_wall", "story.visits"), which is how
|
|
1202
|
+
* `listProperties()` prints them. */
|
|
1203
|
+
properties?: ContractProperty[];
|
|
1204
|
+
/** Card-template field names the crew and the bridges read. */
|
|
1205
|
+
fields?: string[];
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* One contracted property.
|
|
1209
|
+
*
|
|
1210
|
+
* A bare path is the common form and the one the spec's example writes. The
|
|
1211
|
+
* object form adds the TYPE the venue was provisioned against, which is the only
|
|
1212
|
+
* way `validate` can catch the break that costs a producer most: a property that
|
|
1213
|
+
* still exists under the same name and now holds something else. A server that
|
|
1214
|
+
* knows the type should write the object form; a hand-written contract may say
|
|
1215
|
+
* only the path and get the existence check alone.
|
|
1216
|
+
*/
|
|
1217
|
+
type ContractProperty = string | {
|
|
1218
|
+
path: string;
|
|
1219
|
+
type?: PropertyType;
|
|
1220
|
+
};
|
|
1221
|
+
/** The path a contracted property names, whichever form it was written in. */
|
|
1222
|
+
declare const contractPropertyPath: (p: ContractProperty) => string;
|
|
1223
|
+
/** The type a contracted property was provisioned against, when it says. */
|
|
1224
|
+
declare const contractPropertyType: (p: ContractProperty) => PropertyType | undefined;
|
|
901
1225
|
/** A point in a canvas's own coordinates. */
|
|
902
1226
|
interface ViewPoint {
|
|
903
1227
|
x: number;
|
|
@@ -1017,7 +1341,7 @@ interface ProjectShard {
|
|
|
1017
1341
|
name: string;
|
|
1018
1342
|
version: string;
|
|
1019
1343
|
};
|
|
1020
|
-
settings:
|
|
1344
|
+
settings: ProjectSettings;
|
|
1021
1345
|
/** Coverage drivers + argument domains (authoring/testing config; stays
|
|
1022
1346
|
* out of the compiled bundle). */
|
|
1023
1347
|
coverage?: CoverageConfig;
|
|
@@ -1087,6 +1411,10 @@ interface BoxShard {
|
|
|
1087
1411
|
ranking: {
|
|
1088
1412
|
specificity: boolean;
|
|
1089
1413
|
};
|
|
1414
|
+
/** Declares a timed box (see `Box.turn`); compiled through unchanged. */
|
|
1415
|
+
turn?: {
|
|
1416
|
+
seconds: number;
|
|
1417
|
+
};
|
|
1090
1418
|
fields: FieldDecl[];
|
|
1091
1419
|
properties: PropertyDecl[];
|
|
1092
1420
|
};
|
|
@@ -1112,6 +1440,8 @@ interface DeckShard {
|
|
|
1112
1440
|
condition?: string;
|
|
1113
1441
|
/** Scarce across flows: see Deck.shared. */
|
|
1114
1442
|
shared?: boolean;
|
|
1443
|
+
/** Its `redraw: "never"` cards are spent past the run: see Deck.durable. */
|
|
1444
|
+
durable?: boolean;
|
|
1115
1445
|
/** Authored display order within the box (sparse; see BoxShard). */
|
|
1116
1446
|
order?: number;
|
|
1117
1447
|
properties: PropertyDecl[];
|
|
@@ -1119,4 +1449,4 @@ interface DeckShard {
|
|
|
1119
1449
|
cards: Card<string>[];
|
|
1120
1450
|
}
|
|
1121
1451
|
|
|
1122
|
-
export { BOX_SCHEMA, BUNDLE_EXTENSION, BUNDLE_SCHEMA, type Box, type BoxMap, type BoxShard, type Bundle, type BundleBackground, type BundleContent, type BundleMap, type BundleSettings, type CanvasFurniture, type Card, type Comment, type CommentMark, type CommentMessage, type CoverageConfig, type CoverageDriver, DECK_SCHEMA, type Deck, type DeckCanvas, type DeckShard, FURNITURE_COLOURS, type FieldDecl, type FlowSave, type Frame, type FurnitureColour, HANDS_SCHEMA, type Hand, type HandBinding, type HandRule, type HandTemplate, type HandsShard, NOTES_SCHEMA, type NotesShard, type Outcome, PLACE_GROUP, PROJECT_FOLDER_EXTENSION, PROJECT_SCHEMA, type PlayRecord, type Polygon, type ProjectShard, type PropertyBag, type PropertyDecl, type PropertyType, type PropsPartition, type Rect, type RedrawPolicy, SAVEFILE_SCHEMA, SAVE_SCHEMA, SHARD_EXTENSIONS, SPATIAL, type SaveEnvelope, type SaveFile, type SharedSave, type SpatialBackground, type SpatialGroup, type StackMove, type Stacked, TAGS_SCHEMA, type Tag, type TagGroup, type TagsShard, VIEW_SCHEMA, type ViewPoint, type ViewShard, backgroundsOf, bindHand, bundleAssetPath, byDisplayOrder, centroid, commentsOf, droppedRect, effectiveGameId, framesOf, freeGameId, freeTitle, gameIdify, handBinding, inferDeclFromWrite, isSpatial, isValidGameId, labelPoint, markOf, marksOn, openThreadCounts, pointInPolygon, polygonBounds, polygonOf, restack, spatialOf, stacked, threadsFor, unbindHand, withBackgrounds, withPolygon, withSpatialGroup, withZ, zOf, zoneAt, zonesAt };
|
|
1452
|
+
export { BOX_SCHEMA, BUNDLE_EXTENSION, BUNDLE_SCHEMA, type Box, type BoxMap, type BoxShard, type Bundle, type BundleBackground, type BundleContent, type BundleMap, type BundleSettings, CONTRACTS_DIR, CONTRACT_SCHEMA, type CanvasFurniture, type Card, type Comment, type CommentMark, type CommentMessage, type ContractProperty, type ContractShard, type CoverageConfig, type CoverageDriver, DECK_SCHEMA, DEFAULT_PLAY_RUNG, type Deck, type DeckCanvas, type DeckShard, FURNITURE_COLOURS, type FieldDecl, type FlowSave, type Frame, type FurnitureColour, HANDS_SCHEMA, type Hand, type HandBinding, type HandRule, type HandTemplate, type HandsShard, type HoleRef, type HoleRefScope, type LoadEviction, type LoadProperty, type LoadReport, NOTES_SCHEMA, type NotesShard, type Outcome, PLACE_GROUP, PROJECT_FOLDER_EXTENSION, PROJECT_SCHEMA, type PlayRecord, type PlayRung, type Polygon, type ProjectSettings, type ProjectShard, type PropertyBag, type PropertyDecl, type PropertyType, type PropsPartition, type Rect, type RedrawPolicy, SAVEFILE_SCHEMA, SAVE_SCHEMA, SHARD_EXTENSIONS, SPATIAL, type SaveEnvelope, type SaveFile, type SharedSave, type SpatialBackground, type SpatialGroup, type StackMove, type Stacked, TAGS_SCHEMA, type Tag, type TagGroup, type TagsShard, VIEW_SCHEMA, type ValueAddresses, type ViewPoint, type ViewShard, ambiguousValueAddressMessage, backgroundsOf, bindHand, bundleAssetPath, byDisplayOrder, centroid, commentsOf, contractPropertyPath, contractPropertyType, droppedRect, effectiveGameId, framesOf, freeGameId, freeTitle, gameIdify, handBinding, inferDeclFromWrite, isHoleRef, isSpatial, isValidGameId, labelPoint, markOf, marksOn, openThreadCounts, parseHoleRef, pointInPolygon, polygonBounds, polygonOf, restack, spatialOf, stacked, threadsFor, turnSpan, unbindHand, valueAddresses, withBackgrounds, withPolygon, withSpatialGroup, withZ, zOf, zoneAt, zonesAt };
|
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";
|
|
@@ -431,8 +490,16 @@ var SHARD_EXTENSIONS = {
|
|
|
431
490
|
* id-keyed (design/annotation.md). Documentation NOTES used to share this
|
|
432
491
|
* file and were retired: `purpose` already says why a thing exists, and
|
|
433
492
|
* Patterpad's typed routing has no destination here. */
|
|
434
|
-
notes: ".storyletnotes"
|
|
493
|
+
notes: ".storyletnotes",
|
|
494
|
+
/** An installation contract: what a VENUE depends on, one file per
|
|
495
|
+
* installation in `contracts/` at the project root
|
|
496
|
+
* (design/engine-server.md 4.11). Its own shard, and its own folder, for the
|
|
497
|
+
* walkthrough's reason (Reboot 7.5, S4): a different owner, a different
|
|
498
|
+
* change rate, and a merge that must never collide with the author's edits,
|
|
499
|
+
* since the server always wins its own file. */
|
|
500
|
+
contract: ".storyletcontract"
|
|
435
501
|
};
|
|
502
|
+
var CONTRACTS_DIR = "contracts";
|
|
436
503
|
var PROJECT_SCHEMA = "storylets/project@0";
|
|
437
504
|
var BOX_SCHEMA = "storylets/box@0";
|
|
438
505
|
var TAGS_SCHEMA = "storylets/tags@0";
|
|
@@ -440,12 +507,18 @@ var HANDS_SCHEMA = "storylets/hands@0";
|
|
|
440
507
|
var DECK_SCHEMA = "storylets/deck@0";
|
|
441
508
|
var VIEW_SCHEMA = "storylets/view@0";
|
|
442
509
|
var NOTES_SCHEMA = "storylets/notes@0";
|
|
510
|
+
var CONTRACT_SCHEMA = "storylets/contract@0";
|
|
511
|
+
var contractPropertyPath = (p) => typeof p === "string" ? p : p.path;
|
|
512
|
+
var contractPropertyType = (p) => typeof p === "string" ? void 0 : p.type;
|
|
443
513
|
var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
444
514
|
export {
|
|
445
515
|
BOX_SCHEMA,
|
|
446
516
|
BUNDLE_EXTENSION,
|
|
447
517
|
BUNDLE_SCHEMA,
|
|
518
|
+
CONTRACTS_DIR,
|
|
519
|
+
CONTRACT_SCHEMA,
|
|
448
520
|
DECK_SCHEMA,
|
|
521
|
+
DEFAULT_PLAY_RUNG,
|
|
449
522
|
FURNITURE_COLOURS,
|
|
450
523
|
HANDS_SCHEMA,
|
|
451
524
|
NOTES_SCHEMA,
|
|
@@ -459,12 +532,15 @@ export {
|
|
|
459
532
|
SPATIAL,
|
|
460
533
|
TAGS_SCHEMA,
|
|
461
534
|
VIEW_SCHEMA,
|
|
535
|
+
ambiguousValueAddressMessage,
|
|
462
536
|
backgroundsOf,
|
|
463
537
|
bindHand,
|
|
464
538
|
bundleAssetPath,
|
|
465
539
|
byDisplayOrder,
|
|
466
540
|
centroid,
|
|
467
541
|
commentsOf,
|
|
542
|
+
contractPropertyPath,
|
|
543
|
+
contractPropertyType,
|
|
468
544
|
droppedRect,
|
|
469
545
|
effectiveGameId,
|
|
470
546
|
framesOf,
|
|
@@ -474,6 +550,7 @@ export {
|
|
|
474
550
|
handBinding,
|
|
475
551
|
inferDeclFromWrite,
|
|
476
552
|
isCaseOnlyPropertyName,
|
|
553
|
+
isHoleRef,
|
|
477
554
|
isSpatial,
|
|
478
555
|
isValidGameId,
|
|
479
556
|
isValidPropertyName,
|
|
@@ -481,6 +558,7 @@ export {
|
|
|
481
558
|
markOf,
|
|
482
559
|
marksOn,
|
|
483
560
|
openThreadCounts,
|
|
561
|
+
parseHoleRef,
|
|
484
562
|
pointInPolygon,
|
|
485
563
|
polygonBounds,
|
|
486
564
|
polygonOf,
|
|
@@ -489,7 +567,9 @@ export {
|
|
|
489
567
|
spatialOf,
|
|
490
568
|
stacked,
|
|
491
569
|
threadsFor,
|
|
570
|
+
turnSpan,
|
|
492
571
|
unbindHand,
|
|
572
|
+
valueAddresses,
|
|
493
573
|
withBackgrounds,
|
|
494
574
|
withPolygon,
|
|
495
575
|
withSpatialGroup,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storylet-studio/model",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|