@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.d.ts CHANGED
@@ -409,10 +409,30 @@ 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
- /** A card-template field (box-defined). Data for the host; the engine never
415
- * interprets fields and they are not addressable from expressions. */
433
+ /** A template field (box-defined), of the card template or of the outcome
434
+ * fields. Data for the host; the engine never interprets fields and they are
435
+ * not addressable from expressions. */
416
436
  interface FieldDecl {
417
437
  name: string;
418
438
  type: PropertyType;
@@ -433,6 +453,32 @@ declare function effectiveGameId(entity: {
433
453
  title?: string;
434
454
  id: string;
435
455
  }): string;
456
+ /** The three answers a value address needs, all derived from the bundle. */
457
+ interface ValueAddresses {
458
+ /** Tag internal id -> the owner segment an address PRINTS for it. */
459
+ print: Map<string, string>;
460
+ /** Every owner segment a value address ACCEPTS -> the tag's internal id.
461
+ * Holds the qualified form for every tag and the short form only for a
462
+ * gameId no other tag shares. */
463
+ accept: Map<string, string>;
464
+ /** A tag gameId more than one box uses -> its qualified forms, in bundle
465
+ * order. Empty for the overwhelming majority of projects, and what a
466
+ * refusal lists. */
467
+ repeated: Map<string, string[]>;
468
+ }
469
+ /** The owner segment of every tag in the bundle, both ways round. */
470
+ declare function valueAddresses(bundle: {
471
+ boxes: readonly {
472
+ id: string;
473
+ gameId?: string;
474
+ title?: string;
475
+ tagGroups: readonly TagGroup[];
476
+ }[];
477
+ }): ValueAddresses;
478
+ /** What an ambiguous short-form value address is told: the candidates, in
479
+ * full, because "that names two tags" without them leaves a host reading a
480
+ * bundle it did not write to find out which boxes. */
481
+ declare function ambiguousValueAddressMessage(segment: string, name: string, candidates: readonly string[]): string;
436
482
  /**
437
483
  * The first free gameId of the form `base`, `base-2`, `base-3`, ... not already
438
484
  * in `taken`.
@@ -473,6 +519,17 @@ declare function byDisplayOrder<T extends {
473
519
  order?: number;
474
520
  }>(items: readonly T[]): T[];
475
521
  declare function freeTitle(base: string, taken: ReadonlySet<string>): string;
522
+ /**
523
+ * A count of a TIMED box's turns, said as time (design/engine-server.md 4.8):
524
+ * `turnSpan(30, 60)` is "30 min", and `turnSpan(30, 60, true)` is "30 minutes".
525
+ *
526
+ * One definition, because the conversion appears wherever a designer might
527
+ * otherwise have to do it in their head: the card editor's Redraw field, the
528
+ * box page, the Board's advance buttons, and the coverage report's turn
529
+ * budget. Two of those want the unit spelled out and two want it short, which
530
+ * is the whole of `long`.
531
+ */
532
+ declare function turnSpan(turns: number, seconds: number, long?: boolean): string;
476
533
  interface Outcome<E> {
477
534
  id: string;
478
535
  gameId?: string;
@@ -488,6 +545,11 @@ interface Outcome<E> {
488
545
  /** Target ("@scope.name") -> expression; all right-hand sides evaluate
489
546
  * against pre-play state (schema 3.7). */
490
547
  changes: Record<string, E>;
548
+ /** Template data, as `Card.fields` is: field name -> value, declared by
549
+ * the box's `outcomeFields`, validated at publish, and handed to the host
550
+ * with the outcome. The engine never reads it: a press can say one line
551
+ * ("The notice is in your pocket") without spending a card on it. */
552
+ fields?: Record<string, ScalarValue>;
491
553
  }
492
554
  interface Card<E> {
493
555
  id: string;
@@ -528,6 +590,21 @@ interface Card<E> {
528
590
  * common case writes one number and "five in the world, one to a customer"
529
591
  * is `copies: 1, sharedCopies: 5`. */
530
592
  sharedCopies?: number;
593
+ /** Does this card's `redraw: "never"` spend survive the run
594
+ * (design/engine-server.md 4.2)? Absent takes the deck's flag, set here it
595
+ * overrides the deck, exactly as `shared` does. `shared` decides who a
596
+ * spend counts for WITHIN a run; this decides whether it outlives one.
597
+ *
598
+ * Only `"never"` crosses the run boundary, for the reason only `"never"`
599
+ * crosses the flow boundary (shared-scarcity 9.3.2): a finite cooldown is
600
+ * an absolute turn of a box clock, and the clock resets with the run. On
601
+ * any other redraw the flag is a compile warning.
602
+ *
603
+ * INERT TO THE RUNTIME, like the declaration flag: the server lifts the
604
+ * durable spends at run end (per-flow ones from the flow's `never`
605
+ * cooldowns, shared ones from the engine's spent set) and puts them back
606
+ * through `openFlow(id, { restore })` and `markTaken`. */
607
+ durable?: boolean;
531
608
  /** Card-template data: field name -> value, validated at publish. */
532
609
  fields?: Record<string, ScalarValue>;
533
610
  outcomes: Outcome<E>[];
@@ -543,6 +620,11 @@ interface Deck<E> {
543
620
  * in it is shared unless the card says otherwise. The container is where
544
621
  * Patter puts its own shared-memory flag, and the deck is our container. */
545
622
  shared?: boolean;
623
+ /** Every `redraw: "never"` card in this pile is spent for good, past the end
624
+ * of the run, unless the card says otherwise (design/engine-server.md 4.2).
625
+ * The container carries the flag for the reason `shared` is carried here:
626
+ * a pile is what an author reaches for when a rule is true of all of it. */
627
+ durable?: boolean;
546
628
  properties: PropertyDecl[];
547
629
  cards: Card<E>[];
548
630
  }
@@ -638,6 +720,30 @@ interface TagGroup {
638
720
  * half of that answer. `home` was a metaphor an author had to learn, and it
639
721
  * leaked into hand-edited shards and the docs. */
640
722
  declare const PLACE_GROUP = "place";
723
+ /** The scopes a movable hole may be filled from (design/engine-server.md 4.6):
724
+ * the two `boundBy` already allows, plus `@hand` - the asking hand's OWN
725
+ * declared property, resolved before tag composition so a movable hole can
726
+ * never depend on the tags it is choosing. */
727
+ type HoleRefScope = "hand" | "story" | "world";
728
+ /** A parsed hole reference: `@hand.zone` -> `{ scope: "hand", name: "zone" }`. */
729
+ interface HoleRef {
730
+ scope: HoleRefScope;
731
+ name: string;
732
+ }
733
+ /**
734
+ * Is this `chosen` / binding value MEANT as a property reference rather than a
735
+ * tag id?
736
+ *
737
+ * The test is the leading `@` alone, deliberately: a value that starts with
738
+ * one and does not parse is a mistyped reference, which the compiler should
739
+ * name as such, not a tag id that happens to look odd. Tag ids never begin
740
+ * with `@`.
741
+ */
742
+ declare const isHoleRef: (value: string) => boolean;
743
+ /** Parse a hole reference, or undefined when it is not one. The on-disk form
744
+ * stays a plain string, so the canonical serialiser and the shard merge need
745
+ * no change at all: a hole is still one group name against one value. */
746
+ declare const parseHoleRef: (value: string) => HoleRef | undefined;
641
747
  /** A declared kind of hand (schema 2.6): live-inherited, author-side only,
642
748
  * never called from game code. One condition governs every instance. */
643
749
  interface HandTemplate<E> {
@@ -649,9 +755,12 @@ interface HandTemplate<E> {
649
755
  * position). Merges as a per-item value, so id-sorted storage stays
650
756
  * merge-clean (Reboot 7.4). */
651
757
  order?: number;
652
- /** Fixed tag bindings: tag group id -> tag id. */
758
+ /** Fixed tag bindings: tag group id -> tag id. Literal tags only: what a
759
+ * template FIXES is the same for every instance, and a hole that moves is
760
+ * the instance's own business (`Hand.chosen`, 4.6). */
653
761
  bindings?: Record<string, string>;
654
- /** The holes: tag group ids each instance fills (one tag each). */
762
+ /** The holes: tag group ids each instance fills (one tag each, or one
763
+ * property reference: 4.6). */
655
764
  chooses?: string[];
656
765
  /** Shared availability condition, ANDed in (schema 3.1); evaluated per
657
766
  * instance against that instance's composed @hand. */
@@ -663,6 +772,15 @@ interface HandTemplate<E> {
663
772
  }
664
773
  /** A standalone hand's inline rule (schema 2.6): owned by the hand. */
665
774
  interface HandRule<E> {
775
+ /**
776
+ * Tag group id -> tag id, or a PROPERTY REFERENCE (`"@hand.zone"`,
777
+ * `"@story.where"`, `"@world.place"`) the runtime resolves at ask time
778
+ * (design/engine-server.md 4.6, the hand that moves). Still a plain string
779
+ * on disk, so the canonical serialiser and the merge are untouched; what
780
+ * widened is the meaning, and `parseHoleRef` is where it is read.
781
+ *
782
+ * `place` is never fillable this way: it is the hand's own name, not an axis.
783
+ */
666
784
  bindings?: Record<string, string>;
667
785
  condition?: E;
668
786
  slots: number | "unbounded";
@@ -679,7 +797,17 @@ interface Hand<E> {
679
797
  purpose?: string;
680
798
  /** Hand template id (not gameId). */
681
799
  template?: string;
682
- /** Template instances: tag group id -> tag id, one per `chooses` hole. */
800
+ /**
801
+ * Template instances: tag group id -> tag id, one per `chooses` hole.
802
+ *
803
+ * A value may instead be a PROPERTY REFERENCE (`"@hand.zone"`,
804
+ * `"@story.where"`, `"@world.place"`), which makes the hole MOVABLE: the
805
+ * runtime resolves the reference at ask time and binds the hole to the tag
806
+ * the value names, so moving the Elder to the forest is `setProperty` and
807
+ * nothing else (design/engine-server.md 4.6). Still a plain string on disk,
808
+ * so the canonical serialiser and the shard merge need no change; read it
809
+ * with `parseHoleRef`.
810
+ */
683
811
  chosen?: Record<string, string>;
684
812
  /** Standalone hands: the inline rule. */
685
813
  rule?: HandRule<E>;
@@ -704,8 +832,31 @@ interface Box<E> {
704
832
  ranking: {
705
833
  specificity: boolean;
706
834
  };
835
+ /**
836
+ * A TIMED box: its clock counts real time, one turn every `seconds` of the
837
+ * run (design/engine-server.md 4.8). Absent is the ordinary box, whose turn
838
+ * is a play.
839
+ *
840
+ * Two things follow, and only two. In the ENGINE, a play in this box
841
+ * defaults to advancing nothing: `settings.playAdvancesTurns` does not
842
+ * apply, so a designer cannot declare the convention and then forget to
843
+ * switch play-advance off. Everywhere else it is what the tools SAY: the
844
+ * host ticks the box (the runtime has no clock and gains none here), and a
845
+ * card's `redraw: N` reads as N x `seconds`, which the editors, the bundle
846
+ * inspectors and the coverage report spell out rather than leaving a
847
+ * designer to know that 30 meant minutes.
848
+ *
849
+ * The number itself is inert to the runtime, which never reads it.
850
+ */
851
+ turn?: {
852
+ seconds: number;
853
+ };
707
854
  /** The card template: what every card in this box carries. */
708
855
  fields: FieldDecl[];
856
+ /** What every outcome in this box may carry, declared the same way.
857
+ * Absent when the box declares none, so a bundle without them is byte for
858
+ * byte what it was. */
859
+ outcomeFields?: FieldDecl[];
709
860
  properties: PropertyDecl[];
710
861
  tagGroups: TagGroup[];
711
862
  decks: Deck<E>[];
@@ -723,6 +874,30 @@ interface BundleContent {
723
874
  interface BundleSettings {
724
875
  playAdvancesTurns: number;
725
876
  }
877
+ /**
878
+ * The play ladder (design/engine-server.md 4.10): how much of itself
879
+ * Storyletter shows this project, in one setting with three rungs rather than
880
+ * a set of toggles, because the features nest.
881
+ *
882
+ * solo one player, one flow: no sharing, no durability, no venue features
883
+ * shared several players over one world: sharing appears
884
+ * venue a production: nothing is hidden
885
+ *
886
+ * EDITOR-SIDE ONLY. It stays in the project shard beside `coverage` and
887
+ * `export` and is never compiled: a solo project plays on the same Engine as a
888
+ * venue one. Hidden is hidden rather than disabled, so going DOWN a rung is
889
+ * refused when the project already contains what the rung would hide, and a
890
+ * hand-edited shard above its rung is a compile warning.
891
+ */
892
+ type PlayRung = "solo" | "shared" | "venue";
893
+ /** The default rung: a project shard that says nothing is a solo game. */
894
+ declare const DEFAULT_PLAY_RUNG: PlayRung;
895
+ /** The project shard's settings block: what the bundle carries, plus the
896
+ * authoring-side play rung that it does not. */
897
+ interface ProjectSettings extends BundleSettings {
898
+ /** The play ladder rung (see `PlayRung`). Absent = "solo". */
899
+ play?: PlayRung;
900
+ }
726
901
  /**
727
902
  * A map that a bundle was asked to carry: one spatial tag group's geometry,
728
903
  * flattened for a host to draw (design/graphical-views.md 2, "The map MAY ship").
@@ -737,9 +912,16 @@ interface BundleSettings {
737
912
  * names it passes to `peek`. There is nothing here to strip either, which is why
738
913
  * `metadata: "stripped"` needs no special case: no titles, no purposes.
739
914
  *
740
- * Sites are deliberately NOT here. A site is where an author parked a hand while
741
- * working, held in the view sidecar precisely because it is not content, and a
742
- * host that wants to place a hand already has its zone from the compiled binding.
915
+ * SITES ARE HERE, which reverses a ruling. Until 2026-09-05 this comment said
916
+ * they were deliberately not: a site was where an author parked a hand while
917
+ * working, held in the view sidecar precisely because it was not content, and a
918
+ * host that wanted to place a hand had its zone from the compiled binding. That
919
+ * held for a game, where a hand's zone is its only real-world meaning. It does
920
+ * not hold for a physical experience (design/engine-server.md 4.3), where the
921
+ * position IS content: it is where the kiosk stands, and a producer's map is
922
+ * simply wrong without it. The alternative was a second file beside the bundle,
923
+ * which would cost a format the inspectors do not read and would put the view
924
+ * sidecar in the shipping path by the back door.
743
925
  */
744
926
  interface BundleMap {
745
927
  /** The owning box, by gameId (tag groups are box-scoped). */
@@ -755,6 +937,16 @@ interface BundleMap {
755
937
  /** Background pictures, back to front, as bundle-relative paths. Hidden ones
756
938
  * do not ship: what an author put away is not something to spring on a host. */
757
939
  backgrounds?: BundleBackground[];
940
+ /** Where the placed hands stand on this map, by hand gameId, sorted by that
941
+ * gameId so the bytes do not depend on authoring order. A hand nobody has
942
+ * placed has no entry, and a map with no placed hand has no key at all. The
943
+ * zone a site sits in is NOT repeated here: the hand's own binding is what
944
+ * the runtime deals from, and a second copy could only go on to disagree. */
945
+ sites?: {
946
+ hand: string;
947
+ x: number;
948
+ y: number;
949
+ }[];
758
950
  }
759
951
  /** One shipped picture. `locked` and `hidden` are authoring state and do not
760
952
  * travel; the draw order is the array order. */
@@ -844,6 +1036,75 @@ interface SaveEnvelope {
844
1036
  shared: SharedSave;
845
1037
  flows: Record<string, FlowSave>;
846
1038
  }
1039
+ /** One card that a restore refused to put back on the board.
1040
+ *
1041
+ * `vanished` and `hand-vanished` are the edit's doing (the card, or the hand
1042
+ * it sat in, is no longer in the bundle). `claimed-elsewhere` is only ever a
1043
+ * single-flow restore into a LIVE engine: the card is shared, and the other
1044
+ * open flows already hold every copy the world has. */
1045
+ interface LoadEviction {
1046
+ flow: string;
1047
+ hand: string;
1048
+ card: string;
1049
+ reason: "vanished" | "hand-vanished" | "claimed-elsewhere";
1050
+ }
1051
+ /** One property the restore could not put back as it was. `flow` names the
1052
+ * flow whose half it belongs to; absent, it is the shared half.
1053
+ *
1054
+ * `path` is the engine's property address, spelled exactly as
1055
+ * `Flow.listProperties()` / `Engine.listProperties()` print it and exactly as
1056
+ * `getProperty` and `setProperty` accept it: `story.<name>` for the story
1057
+ * scope, `<scope>.<ownerGameId>.<name>` for the box, deck, hand and tag
1058
+ * scopes. No `@`, which belongs to the expression language and not to an
1059
+ * address.
1060
+ *
1061
+ * The owner segment is its GAMEID (design/engine-server.md 4.4), the name it
1062
+ * is called by everywhere else, so an operator reading a hot-swap report can
1063
+ * paste the address straight into `setProperty`. An owner the build no longer
1064
+ * has keeps the id the save carried: there is no gameId left to give it,
1065
+ * which is the rule the eviction list above has always used. */
1066
+ interface LoadProperty {
1067
+ flow?: string;
1068
+ path: string;
1069
+ }
1070
+ /** What a load or a flow restore would do that is not a plain restore
1071
+ * (design/engine-server.md 4.9). Arrays are sorted, so two runtimes given the
1072
+ * same save and bundle produce the same bytes; `flows` alone keeps the
1073
+ * envelope's own order, because a caller re-takes its handles in it. */
1074
+ interface LoadReport {
1075
+ /** No drift and nothing dropped, defaulted or retyped: the save goes back
1076
+ * exactly as it was. `flows` is not a divergence and does not count. */
1077
+ exact: boolean;
1078
+ project: string;
1079
+ /** Drift when the two differ; reported, never refused. */
1080
+ version: {
1081
+ saved: string;
1082
+ bundle: string;
1083
+ };
1084
+ /** Drift when the two differ; reported, never refused. */
1085
+ hash: {
1086
+ saved: string;
1087
+ bundle: string;
1088
+ };
1089
+ /** The flows this restores, in the order it restores them. */
1090
+ flows: string[];
1091
+ evicted: LoadEviction[];
1092
+ /** Cooldowns held for cards the bundle no longer has. */
1093
+ droppedCooldowns: {
1094
+ flow: string;
1095
+ card: string;
1096
+ }[];
1097
+ /** Shared `redraw: "never"` entries for cards the bundle no longer has. */
1098
+ droppedSpent: string[];
1099
+ /** In the save, not declared any more. */
1100
+ droppedProperties: LoadProperty[];
1101
+ /** Declared, not in the save: it takes the declaration's default. */
1102
+ defaultedProperties: LoadProperty[];
1103
+ /** In the save, still declared, but the saved value no longer fits the
1104
+ * declaration (its type changed, or an enum value / quality stage was
1105
+ * edited away). It takes the declaration's default. */
1106
+ retypedProperties: LoadProperty[];
1107
+ }
847
1108
  /** The .storyletsave FILE: the HOST's file, not the engine's - the engine's
848
1109
  * envelope plus, when the host keeps one, its @world container. This is
849
1110
  * "host saves its container once, each engine saves its own envelope"
@@ -876,11 +1137,22 @@ declare const SHARD_EXTENSIONS: {
876
1137
  readonly tags: ".storylettags";
877
1138
  readonly hands: ".storylethands";
878
1139
  readonly deck: ".storyletdeck";
879
- /** The arrangement layer: where things SIT, never what they are. Its own shard
880
- * because positions churn (an afternoon of tidying a canvas touches every
881
- * card) and content does not, so a designer arranging and a writer editing
882
- * never collide on one file (design/graphical-views.md section 1.2). */
1140
+ /** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's
1141
+ * node canvas and the furniture drawn round them. Its own shard because
1142
+ * positions churn (an afternoon of tidying a canvas touches every card) and
1143
+ * content does not, so a designer arranging and a writer editing never
1144
+ * collide on one file (design/graphical-views.md section 1.2). */
883
1145
  readonly view: ".storyletview";
1146
+ /** The DESIGNER's map: where a box's hands stand in space, and the furniture
1147
+ * round them. One per box, beside the view shard.
1148
+ *
1149
+ * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1
1150
+ * point 5) because the two halves stopped having one owner. A hand's
1151
+ * position ships in the bundle's `maps` block (4.3) and is where a venue's
1152
+ * kiosk stands, so it is SHAPE, which a server's author key may not change;
1153
+ * the canvases are the author's own working drawing and never leave the
1154
+ * project folder. One file could not be both. */
1155
+ readonly map: ".storyletmap";
884
1156
  /** Threaded comments: content-ADJACENT, so neither in a content shard (a
885
1157
  * writer's deck edit must not conflict with a reviewer's comment) nor in the
886
1158
  * arrangement sidecar (this is not where anything sits). One per box,
@@ -888,16 +1160,94 @@ declare const SHARD_EXTENSIONS: {
888
1160
  * file and were retired: `purpose` already says why a thing exists, and
889
1161
  * Patterpad's typed routing has no destination here. */
890
1162
  readonly notes: ".storyletnotes";
1163
+ /** An installation contract: what a VENUE depends on, one file per
1164
+ * installation in `contracts/` at the project root
1165
+ * (design/engine-server.md 4.11). Its own shard, and its own folder, for the
1166
+ * walkthrough's reason (Reboot 7.5, S4): a different owner, a different
1167
+ * change rate, and a merge that must never collide with the author's edits,
1168
+ * since the server always wins its own file. */
1169
+ readonly contract: ".storyletcontract";
891
1170
  };
1171
+ /** Where the installation contracts live, relative to the project root. The
1172
+ * directory is the registry, as it is for a box's decks: a contract exists
1173
+ * because its file exists. */
1174
+ declare const CONTRACTS_DIR = "contracts";
892
1175
  declare const PROJECT_SCHEMA = "storylets/project@0";
893
1176
  declare const BOX_SCHEMA = "storylets/box@0";
894
1177
  declare const TAGS_SCHEMA = "storylets/tags@0";
895
1178
  declare const HANDS_SCHEMA = "storylets/hands@0";
896
1179
  declare const DECK_SCHEMA = "storylets/deck@0";
897
1180
  declare const VIEW_SCHEMA = "storylets/view@0";
1181
+ declare const MAP_SCHEMA = "storylets/map@0";
898
1182
  /** The comment sidecar's schema. Still called "notes" on disk: the file already
899
1183
  * held both, and renaming it would break every project for no gain. */
900
1184
  declare const NOTES_SCHEMA = "storylets/notes@0";
1185
+ declare const CONTRACT_SCHEMA = "storylets/contract@0";
1186
+ /**
1187
+ * What one installation depends on, written by the venue's server and read by
1188
+ * `validate` (design/engine-server.md 4.11).
1189
+ *
1190
+ * NOT THE AUTHOR'S FILE. A venue is provisioned against names - the hands its
1191
+ * stations deal, the boxes its scheduler ticks, the properties its clocks drive,
1192
+ * the fields its crew read - and the server writes them out so the tools that
1193
+ * already gate a build can refuse a rename before it reaches the venue. A
1194
+ * project playing at two venues has two of these. The author never edits one,
1195
+ * and today, with no server built, a project either receives one or has none.
1196
+ *
1197
+ * NEVER COMPILED. It is project-side config like `coverage` and `export`: the
1198
+ * server does not need its own contract handed back, it needs the bundle to
1199
+ * still honour it.
1200
+ *
1201
+ * BY GAMEID throughout, because a gameId is the name that crosses the project's
1202
+ * border and an internal id is authoring identity.
1203
+ */
1204
+ interface ContractShard {
1205
+ schema: typeof CONTRACT_SCHEMA;
1206
+ /** The installation this contract speaks for. One file per installation, and
1207
+ * two files naming the same one is an error. */
1208
+ installation: string;
1209
+ /** Who wrote it, for a human reading the file ("Storylet Server 0.1.0"). */
1210
+ by?: string;
1211
+ /** The server's revision when it wrote this. */
1212
+ revision?: number;
1213
+ /** Hands a station is bound to, by gameId: they may not be renamed or
1214
+ * removed. */
1215
+ hands?: string[];
1216
+ /** Timed boxes the venue's scheduler ticks, by box gameId, with the turn unit
1217
+ * in SECONDS it was provisioned against. A box whose unit changed means every
1218
+ * rest on its cards changed meaning. */
1219
+ boxes?: Record<string, {
1220
+ turn: number;
1221
+ }>;
1222
+ /** Property paths the venue reads or drives, in the engine's own address
1223
+ * grammar with no `@` ("world.time_wall", "story.visits"), which is how
1224
+ * `listProperties()` prints them. */
1225
+ properties?: ContractProperty[];
1226
+ /** Card-template field names the crew and the bridges read. */
1227
+ fields?: string[];
1228
+ /** Outcome field names they read, the same way: the after-line a station
1229
+ * shows when a press lands. What `fields` is to the card template, this is
1230
+ * to the box's `outcomeFields`. */
1231
+ outcomeFields?: string[];
1232
+ }
1233
+ /**
1234
+ * One contracted property.
1235
+ *
1236
+ * A bare path is the common form and the one the spec's example writes. The
1237
+ * object form adds the TYPE the venue was provisioned against, which is the only
1238
+ * way `validate` can catch the break that costs a producer most: a property that
1239
+ * still exists under the same name and now holds something else. A server that
1240
+ * knows the type should write the object form; a hand-written contract may say
1241
+ * only the path and get the existence check alone.
1242
+ */
1243
+ type ContractProperty = string | {
1244
+ path: string;
1245
+ type?: PropertyType;
1246
+ };
1247
+ /** The path a contracted property names, whichever form it was written in. */
1248
+ declare const contractPropertyPath: (p: ContractProperty) => string;
1249
+ /** The type a contracted property was provisioned against, when it says. */
1250
+ declare const contractPropertyType: (p: ContractProperty) => PropertyType | undefined;
901
1251
  /** A point in a canvas's own coordinates. */
902
1252
  interface ViewPoint {
903
1253
  x: number;
@@ -960,7 +1310,9 @@ interface DeckCanvas extends CanvasFurniture {
960
1310
  /** Keyed by CARD id. */
961
1311
  cards?: Record<string, ViewPoint>;
962
1312
  }
963
- /** The box's map: where its hands sit in space, and the furniture around them. */
1313
+ /** The box's map: where its hands sit in space, and the furniture around them.
1314
+ * Carried by the MAP shard since 2026-09-06; `ViewShard.map` is the old
1315
+ * address, read for one release and never written. */
964
1316
  interface BoxMap extends CanvasFurniture {
965
1317
  /** Keyed by HAND id. WHERE a site is, and nothing else.
966
1318
  *
@@ -976,7 +1328,8 @@ interface BoxMap extends CanvasFurniture {
976
1328
  * were edited. */
977
1329
  sites?: Record<string, ViewPoint>;
978
1330
  }
979
- /** The arrangement layer for one box: where things SIT, never what they are.
1331
+ /** The AUTHOR's arrangement layer for one box: where cards sit on their decks'
1332
+ * canvases, and the furniture drawn round them.
980
1333
  *
981
1334
  * Its own shard on purpose (design/graphical-views.md section 1.2). Positions
982
1335
  * churn, content does not: an afternoon of tidying a canvas touches every card,
@@ -991,8 +1344,33 @@ interface ViewShard {
991
1344
  schema: typeof VIEW_SCHEMA;
992
1345
  /** Keyed by DECK id: one node canvas each. */
993
1346
  canvases?: Record<string, DeckCanvas>;
1347
+ /** @deprecated The box map's old address, kept for one release and READ ONLY.
1348
+ * A reader that meets it uses it when the box has no `MapShard`, and the
1349
+ * formatter moves it; nothing writes it any more. Removed after the next
1350
+ * release, at which point a map left here is simply lost. */
994
1351
  map?: BoxMap;
995
1352
  }
1353
+ /** The DESIGNER's map for one box: where its hands stand in space.
1354
+ *
1355
+ * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1 point
1356
+ * 5). The two halves had stopped sharing an owner: a hand's position ships in
1357
+ * the bundle's `maps` block (4.3), which makes it the thing a venue provisions
1358
+ * its kiosks against, while a deck's canvas is a working drawing that never
1359
+ * leaves the folder. A server's author key may change the canvases and not
1360
+ * this.
1361
+ *
1362
+ * The map is NESTED under `map` rather than flattened to the top level, and
1363
+ * deliberately: the block's bytes are then exactly what the view shard held, so
1364
+ * the migration is a move of a value rather than a reshaping of it, the merge
1365
+ * strategy carries over word for word, and a reader that has to look in both
1366
+ * places is one expression (`box.map?.map ?? box.view?.map`).
1367
+ *
1368
+ * Source-only in the sense the view shard is not: `compileMaps` reads the
1369
+ * positions for the bundle's `maps` block, under `export.map`. */
1370
+ interface MapShard {
1371
+ schema: typeof MAP_SCHEMA;
1372
+ map: BoxMap;
1373
+ }
996
1374
  /** A coverage input driver: during a coverage run the harness feeds a
997
1375
  * host-seam property (`@world.x`) values from `values`, so content gated on
998
1376
  * external state gets exercised (Patter's coverageDrivers, carried whole). */
@@ -1017,7 +1395,7 @@ interface ProjectShard {
1017
1395
  name: string;
1018
1396
  version: string;
1019
1397
  };
1020
- settings: BundleSettings;
1398
+ settings: ProjectSettings;
1021
1399
  /** Coverage drivers + argument domains (authoring/testing config; stays
1022
1400
  * out of the compiled bundle). */
1023
1401
  coverage?: CoverageConfig;
@@ -1087,7 +1465,14 @@ interface BoxShard {
1087
1465
  ranking: {
1088
1466
  specificity: boolean;
1089
1467
  };
1468
+ /** Declares a timed box (see `Box.turn`); compiled through unchanged. */
1469
+ turn?: {
1470
+ seconds: number;
1471
+ };
1090
1472
  fields: FieldDecl[];
1473
+ /** The outcome fields (see `Box.outcomeFields`); a shard without the key
1474
+ * declares none. */
1475
+ outcomeFields?: FieldDecl[];
1091
1476
  properties: PropertyDecl[];
1092
1477
  };
1093
1478
  }
@@ -1112,6 +1497,8 @@ interface DeckShard {
1112
1497
  condition?: string;
1113
1498
  /** Scarce across flows: see Deck.shared. */
1114
1499
  shared?: boolean;
1500
+ /** Its `redraw: "never"` cards are spent past the run: see Deck.durable. */
1501
+ durable?: boolean;
1115
1502
  /** Authored display order within the box (sparse; see BoxShard). */
1116
1503
  order?: number;
1117
1504
  properties: PropertyDecl[];
@@ -1119,4 +1506,4 @@ interface DeckShard {
1119
1506
  cards: Card<string>[];
1120
1507
  }
1121
1508
 
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 };
1509
+ 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, MAP_SCHEMA, type MapShard, 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 };