@storylet-studio/runtime 0.8.1 → 0.9.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.cts CHANGED
@@ -265,6 +265,7 @@ interface OwnerIndex {
265
265
  gameId: Map<string, string>;
266
266
  id: Map<string, string>;
267
267
  repeated: Map<string, string[]>;
268
+ zoneQualified: Map<string, string>;
268
269
  }
269
270
  type OwnerIndexes = Record<OwnedScope, OwnerIndex>;
270
271
  /** One side's five stores (shared on the engine, per-flow on each flow). */
@@ -297,9 +298,11 @@ interface Internals {
297
298
  /** The owner segment of a property address, both ways round (4.4). */
298
299
  owners: OwnerIndexes;
299
300
  templatesById: Map<string, HandTemplate<Expression>>;
301
+ /** Every group by internal id. `box` is absent for the project map's group,
302
+ * which belongs to no box. */
300
303
  groupsById: Map<string, {
301
304
  group: TagGroup;
302
- box: Box<Expression>;
305
+ box?: Box<Expression>;
303
306
  }>;
304
307
  requiredGroups: Set<string>;
305
308
  nodeCache: WeakMap<Expression, ExprNode>;
@@ -507,6 +510,20 @@ declare class Engine {
507
510
  * `registry`, self-backed @world included; a game that passed a registry
508
511
  * saves it once itself, beside each engine's envelope. */
509
512
  saveGame(): SaveEnvelope;
513
+ /** The registry's values in CANONICAL order, the order a load rebuilds them
514
+ * in: the engine-wide keys as the constructor registered them, then each
515
+ * flow's keys in `flows()` order (each flow's own registration order), then
516
+ * anything else the registry holds (values still waiting for a key), as the
517
+ * registry lists it. The registry itself lists keys in registration order,
518
+ * and a flow replaced in place (`open()` above keeps its slot in
519
+ * `flowsById`) re-registers its keys at the END, so `openFlow("a");
520
+ * openFlow("b"); openFlow("a")` saved b's keys before a's while a load
521
+ * rebuilt a's first: the same run, different `.storyletsave` bytes, and a
522
+ * save loaded and saved again no longer equal to itself. It is the
523
+ * 2026-08-29 rule carried into the section save@2 moved the per-flow values
524
+ * to (2026-10-01). Order does not matter on READ (`partitionsFromSections`
525
+ * sorts by key shape), so a save written in the old order loads as before. */
526
+ private registrySection;
510
527
  /** ONE flow's blob, to park a visit that is walking away: the same shape
511
528
  * the envelope carries per flow, and the same shape `openFlow`'s `restore`
512
529
  * option takes back (design/engine-server.md 4.1). Saving the whole
@@ -618,7 +635,13 @@ declare class Flow {
618
635
  /** Tag group names are box-scoped: two boxes may name a group the same way
619
636
  * (schema 1 - boxes namespace their groups), so a name is only ever
620
637
  * resolved inside the box being asked, never bundle-wide. Ids are
621
- * project-unique and accepted here too, still confined to the box. */
638
+ * project-unique and accepted here too, still confined to the box.
639
+ *
640
+ * A box on the project map sees ONE namespace: its own groups, then the
641
+ * map's group (design/project-map-contract.md 3.1). A box that has not
642
+ * opted in does not see the map's name at all, so a peek naming it there
643
+ * is the ordinary unknown-group refusal. Own groups first is stated for
644
+ * determinism only: a bundle that loads never has the two share a name. */
622
645
  private groupInBox;
623
646
  /** Fold one play into the indexes. O(the card's tags), not O(the log). */
624
647
  private indexPlay;
@@ -626,8 +649,10 @@ declare class Flow {
626
649
  * rather than appended to, which is `restore` alone. */
627
650
  private rebuildPlayIndex;
628
651
  /** `box` is the box whose ask is being evaluated: the play-history
629
- * functions take a bare group name, so it resolves there (a card's tags
630
- * reference its own box's group, which keeps the counts box-local).
652
+ * functions take a bare group name, so it resolves there, and they count
653
+ * only that box's own plays (the box is in the index key). That was
654
+ * automatic while every group was a box's; a project-map zone is shared,
655
+ * and its history is still not (design/project-map-contract.md 3.7, D7).
631
656
  * History is THIS flow's: countPlayed answers "have I done this". */
632
657
  /** One host per box, built once.
633
658
  *
@@ -816,7 +841,8 @@ declare class Flow {
816
841
 
817
842
  /** What bundle this is: the staleness/identity triple plus the schema tag. */
818
843
  interface BundleIdentity {
819
- /** The bundle schema tag ("storylets/bundle@0"). */
844
+ /** The bundle schema tag ("storylets/bundle@1", or "@0" from before the
845
+ * project map). */
820
846
  schema: string;
821
847
  /** content.project - the project name a save must agree with. */
822
848
  project: string;
@@ -866,6 +892,10 @@ interface TagGroupSummary {
866
892
  interface BoxSummary {
867
893
  gameId: string;
868
894
  title?: string;
895
+ /** The box is on the project map (design/project-map-contract.md 3.7): it
896
+ * may name the map's group in peek criteria beside its own `tagGroups`,
897
+ * which list the box's OWN groups only. Absent is "not on the map". */
898
+ usesMap?: true;
869
899
  /** The only per-box ranking policy (Reboot 2.2). */
870
900
  ranking: {
871
901
  specificity: boolean;
@@ -911,7 +941,8 @@ interface PropertySummary {
911
941
  type PropertyScopeKind = "world" | "story" | "box" | "deck" | "hand" | "tag";
912
942
  /** One scope's declared properties. `owner` is the owning entity's gameId
913
943
  * (empty for world / story); `box` names its box; `group` names a tag's
914
- * group. */
944
+ * group. A zone of the project map is a `tag` scope with `group` and NO
945
+ * `box`: it belongs to none. */
915
946
  interface PropertyScopeSummary {
916
947
  scope: PropertyScopeKind;
917
948
  owner: string;
@@ -920,23 +951,28 @@ interface PropertyScopeSummary {
920
951
  properties: PropertySummary[];
921
952
  }
922
953
  /**
923
- * One map the bundle was asked to carry (design/graphical-views.md 2).
954
+ * The project map (design/project-map-contract.md 3.7): its group, which boxes
955
+ * are on it, and how much geometry the bundle carries.
924
956
  *
925
957
  * Counts rather than the geometry itself, which is the same judgement the rest
926
958
  * of this file makes: an inspector answers "what is in here", and a host that
927
- * wants the polygons reads `bundle.maps` directly. Reported because a bundle
928
- * that silently carried a map would fail the promise this API exists for.
959
+ * wants the polygons reads `bundle.map.geometry` directly. The geometry counts
960
+ * are zero when the build did not ask for geometry (`export.map`); the group is
961
+ * there regardless, because hands and cards reference it.
929
962
  */
930
963
  interface MapSummary {
931
- /** The owning box, by gameId. */
932
- box: string;
933
- /** The tag group this is a map of, by gameId. */
964
+ /** The zone group's gameId: the name an opted-in box's peek criteria use. */
934
965
  group: string;
966
+ /** Its tags (the zones), by gameId. */
967
+ tags: string[];
968
+ /** The opted-in boxes, by gameId, in bundle order. */
969
+ boxes: string[];
970
+ /** Drawn zones in the carried geometry. */
935
971
  zones: number;
936
972
  backgrounds: number;
937
- /** Placed hands standing on this map (design/engine-server.md 4.3): where the
938
- * kiosks are, in a bundle that carries geometry at all. */
939
- sites: number;
973
+ /** Box gameId -> placed hands standing on the map (design/engine-server.md
974
+ * 4.3): where the kiosks are. Only boxes with a site have a key. */
975
+ sites: Record<string, number>;
940
976
  }
941
977
  /** What a bundle offers a host, read from the asset alone. */
942
978
  interface BundleDescription {
@@ -953,13 +989,13 @@ interface BundleDescription {
953
989
  boxes: BoxSummary[];
954
990
  /** Every hand in the bundle, box by box: the deal() surface. */
955
991
  hands: HandSummary[];
956
- /** world, story, then per box: the box, its decks, its hands, its tags.
957
- * Scopes that declare nothing are omitted (world and story always show,
992
+ /** world, story, then per box: the box, its decks, its hands, its tags;
993
+ * then the project map's zones, once. Scopes that declare nothing are omitted (world and story always show,
958
994
  * so their absence reads as "this bundle declares none"). */
959
995
  properties: PropertyScopeSummary[];
960
- /** Maps carried as inert payload, when the build asked for them. Empty is
961
- * the normal state and means the bundle has no geometry in it. */
962
- maps: MapSummary[];
996
+ /** The project map, when the bundle has one. Absent is the bundle with no
997
+ * map at all. */
998
+ map?: MapSummary;
963
999
  }
964
1000
  /** Describe a compiled bundle: the callable surface of an imported asset, no
965
1001
  * session required (design/engine-runtimes.md 2, piece 6). Bundle order
package/dist/index.d.ts CHANGED
@@ -265,6 +265,7 @@ interface OwnerIndex {
265
265
  gameId: Map<string, string>;
266
266
  id: Map<string, string>;
267
267
  repeated: Map<string, string[]>;
268
+ zoneQualified: Map<string, string>;
268
269
  }
269
270
  type OwnerIndexes = Record<OwnedScope, OwnerIndex>;
270
271
  /** One side's five stores (shared on the engine, per-flow on each flow). */
@@ -297,9 +298,11 @@ interface Internals {
297
298
  /** The owner segment of a property address, both ways round (4.4). */
298
299
  owners: OwnerIndexes;
299
300
  templatesById: Map<string, HandTemplate<Expression>>;
301
+ /** Every group by internal id. `box` is absent for the project map's group,
302
+ * which belongs to no box. */
300
303
  groupsById: Map<string, {
301
304
  group: TagGroup;
302
- box: Box<Expression>;
305
+ box?: Box<Expression>;
303
306
  }>;
304
307
  requiredGroups: Set<string>;
305
308
  nodeCache: WeakMap<Expression, ExprNode>;
@@ -507,6 +510,20 @@ declare class Engine {
507
510
  * `registry`, self-backed @world included; a game that passed a registry
508
511
  * saves it once itself, beside each engine's envelope. */
509
512
  saveGame(): SaveEnvelope;
513
+ /** The registry's values in CANONICAL order, the order a load rebuilds them
514
+ * in: the engine-wide keys as the constructor registered them, then each
515
+ * flow's keys in `flows()` order (each flow's own registration order), then
516
+ * anything else the registry holds (values still waiting for a key), as the
517
+ * registry lists it. The registry itself lists keys in registration order,
518
+ * and a flow replaced in place (`open()` above keeps its slot in
519
+ * `flowsById`) re-registers its keys at the END, so `openFlow("a");
520
+ * openFlow("b"); openFlow("a")` saved b's keys before a's while a load
521
+ * rebuilt a's first: the same run, different `.storyletsave` bytes, and a
522
+ * save loaded and saved again no longer equal to itself. It is the
523
+ * 2026-08-29 rule carried into the section save@2 moved the per-flow values
524
+ * to (2026-10-01). Order does not matter on READ (`partitionsFromSections`
525
+ * sorts by key shape), so a save written in the old order loads as before. */
526
+ private registrySection;
510
527
  /** ONE flow's blob, to park a visit that is walking away: the same shape
511
528
  * the envelope carries per flow, and the same shape `openFlow`'s `restore`
512
529
  * option takes back (design/engine-server.md 4.1). Saving the whole
@@ -618,7 +635,13 @@ declare class Flow {
618
635
  /** Tag group names are box-scoped: two boxes may name a group the same way
619
636
  * (schema 1 - boxes namespace their groups), so a name is only ever
620
637
  * resolved inside the box being asked, never bundle-wide. Ids are
621
- * project-unique and accepted here too, still confined to the box. */
638
+ * project-unique and accepted here too, still confined to the box.
639
+ *
640
+ * A box on the project map sees ONE namespace: its own groups, then the
641
+ * map's group (design/project-map-contract.md 3.1). A box that has not
642
+ * opted in does not see the map's name at all, so a peek naming it there
643
+ * is the ordinary unknown-group refusal. Own groups first is stated for
644
+ * determinism only: a bundle that loads never has the two share a name. */
622
645
  private groupInBox;
623
646
  /** Fold one play into the indexes. O(the card's tags), not O(the log). */
624
647
  private indexPlay;
@@ -626,8 +649,10 @@ declare class Flow {
626
649
  * rather than appended to, which is `restore` alone. */
627
650
  private rebuildPlayIndex;
628
651
  /** `box` is the box whose ask is being evaluated: the play-history
629
- * functions take a bare group name, so it resolves there (a card's tags
630
- * reference its own box's group, which keeps the counts box-local).
652
+ * functions take a bare group name, so it resolves there, and they count
653
+ * only that box's own plays (the box is in the index key). That was
654
+ * automatic while every group was a box's; a project-map zone is shared,
655
+ * and its history is still not (design/project-map-contract.md 3.7, D7).
631
656
  * History is THIS flow's: countPlayed answers "have I done this". */
632
657
  /** One host per box, built once.
633
658
  *
@@ -816,7 +841,8 @@ declare class Flow {
816
841
 
817
842
  /** What bundle this is: the staleness/identity triple plus the schema tag. */
818
843
  interface BundleIdentity {
819
- /** The bundle schema tag ("storylets/bundle@0"). */
844
+ /** The bundle schema tag ("storylets/bundle@1", or "@0" from before the
845
+ * project map). */
820
846
  schema: string;
821
847
  /** content.project - the project name a save must agree with. */
822
848
  project: string;
@@ -866,6 +892,10 @@ interface TagGroupSummary {
866
892
  interface BoxSummary {
867
893
  gameId: string;
868
894
  title?: string;
895
+ /** The box is on the project map (design/project-map-contract.md 3.7): it
896
+ * may name the map's group in peek criteria beside its own `tagGroups`,
897
+ * which list the box's OWN groups only. Absent is "not on the map". */
898
+ usesMap?: true;
869
899
  /** The only per-box ranking policy (Reboot 2.2). */
870
900
  ranking: {
871
901
  specificity: boolean;
@@ -911,7 +941,8 @@ interface PropertySummary {
911
941
  type PropertyScopeKind = "world" | "story" | "box" | "deck" | "hand" | "tag";
912
942
  /** One scope's declared properties. `owner` is the owning entity's gameId
913
943
  * (empty for world / story); `box` names its box; `group` names a tag's
914
- * group. */
944
+ * group. A zone of the project map is a `tag` scope with `group` and NO
945
+ * `box`: it belongs to none. */
915
946
  interface PropertyScopeSummary {
916
947
  scope: PropertyScopeKind;
917
948
  owner: string;
@@ -920,23 +951,28 @@ interface PropertyScopeSummary {
920
951
  properties: PropertySummary[];
921
952
  }
922
953
  /**
923
- * One map the bundle was asked to carry (design/graphical-views.md 2).
954
+ * The project map (design/project-map-contract.md 3.7): its group, which boxes
955
+ * are on it, and how much geometry the bundle carries.
924
956
  *
925
957
  * Counts rather than the geometry itself, which is the same judgement the rest
926
958
  * of this file makes: an inspector answers "what is in here", and a host that
927
- * wants the polygons reads `bundle.maps` directly. Reported because a bundle
928
- * that silently carried a map would fail the promise this API exists for.
959
+ * wants the polygons reads `bundle.map.geometry` directly. The geometry counts
960
+ * are zero when the build did not ask for geometry (`export.map`); the group is
961
+ * there regardless, because hands and cards reference it.
929
962
  */
930
963
  interface MapSummary {
931
- /** The owning box, by gameId. */
932
- box: string;
933
- /** The tag group this is a map of, by gameId. */
964
+ /** The zone group's gameId: the name an opted-in box's peek criteria use. */
934
965
  group: string;
966
+ /** Its tags (the zones), by gameId. */
967
+ tags: string[];
968
+ /** The opted-in boxes, by gameId, in bundle order. */
969
+ boxes: string[];
970
+ /** Drawn zones in the carried geometry. */
935
971
  zones: number;
936
972
  backgrounds: number;
937
- /** Placed hands standing on this map (design/engine-server.md 4.3): where the
938
- * kiosks are, in a bundle that carries geometry at all. */
939
- sites: number;
973
+ /** Box gameId -> placed hands standing on the map (design/engine-server.md
974
+ * 4.3): where the kiosks are. Only boxes with a site have a key. */
975
+ sites: Record<string, number>;
940
976
  }
941
977
  /** What a bundle offers a host, read from the asset alone. */
942
978
  interface BundleDescription {
@@ -953,13 +989,13 @@ interface BundleDescription {
953
989
  boxes: BoxSummary[];
954
990
  /** Every hand in the bundle, box by box: the deal() surface. */
955
991
  hands: HandSummary[];
956
- /** world, story, then per box: the box, its decks, its hands, its tags.
957
- * Scopes that declare nothing are omitted (world and story always show,
992
+ /** world, story, then per box: the box, its decks, its hands, its tags;
993
+ * then the project map's zones, once. Scopes that declare nothing are omitted (world and story always show,
958
994
  * so their absence reads as "this bundle declares none"). */
959
995
  properties: PropertyScopeSummary[];
960
- /** Maps carried as inert payload, when the build asked for them. Empty is
961
- * the normal state and means the bundle has no geometry in it. */
962
- maps: MapSummary[];
996
+ /** The project map, when the bundle has one. Absent is the bundle with no
997
+ * map at all. */
998
+ map?: MapSummary;
963
999
  }
964
1000
  /** Describe a compiled bundle: the callable surface of an imported asset, no
965
1001
  * session required (design/engine-runtimes.md 2, piece 6). Bundle order