@craftrpgs/cli 0.2.0 → 0.2.2-alpha.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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/bin.js +1404 -259
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -6301,7 +6301,7 @@ var require_ajv = __commonJS(function(exports, module) {
6301
6301
  } });
6302
6302
  });
6303
6303
 
6304
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
6304
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js
6305
6305
  var require_identity = __commonJS(function(exports) {
6306
6306
  var ALIAS = Symbol.for("yaml.alias");
6307
6307
  var DOC = Symbol.for("yaml.document");
@@ -6355,7 +6355,7 @@ var require_identity = __commonJS(function(exports) {
6355
6355
  exports.isSeq = isSeq;
6356
6356
  });
6357
6357
 
6358
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/visit.js
6358
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/visit.js
6359
6359
  var require_visit = __commonJS(function(exports) {
6360
6360
  var identity = require_identity();
6361
6361
  var BREAK = Symbol("break visit");
@@ -6510,7 +6510,7 @@ var require_visit = __commonJS(function(exports) {
6510
6510
  exports.visitAsync = visitAsync;
6511
6511
  });
6512
6512
 
6513
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/directives.js
6513
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/doc/directives.js
6514
6514
  var require_directives = __commonJS(function(exports) {
6515
6515
  var identity = require_identity();
6516
6516
  var visit = require_visit();
@@ -6662,7 +6662,7 @@ var require_directives = __commonJS(function(exports) {
6662
6662
  exports.Directives = Directives;
6663
6663
  });
6664
6664
 
6665
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/anchors.js
6665
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/doc/anchors.js
6666
6666
  var require_anchors = __commonJS(function(exports) {
6667
6667
  var identity = require_identity();
6668
6668
  var visit = require_visit();
@@ -6724,7 +6724,7 @@ var require_anchors = __commonJS(function(exports) {
6724
6724
  exports.findNewAnchor = findNewAnchor;
6725
6725
  });
6726
6726
 
6727
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/applyReviver.js
6727
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/doc/applyReviver.js
6728
6728
  var require_applyReviver = __commonJS(function(exports) {
6729
6729
  function applyReviver(reviver, obj, key, val) {
6730
6730
  if (val && typeof val === "object") {
@@ -6771,7 +6771,7 @@ var require_applyReviver = __commonJS(function(exports) {
6771
6771
  exports.applyReviver = applyReviver;
6772
6772
  });
6773
6773
 
6774
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/toJS.js
6774
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/toJS.js
6775
6775
  var require_toJS = __commonJS(function(exports) {
6776
6776
  var identity = require_identity();
6777
6777
  function toJS(value, arg, ctx) {
@@ -6798,7 +6798,7 @@ var require_toJS = __commonJS(function(exports) {
6798
6798
  exports.toJS = toJS;
6799
6799
  });
6800
6800
 
6801
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Node.js
6801
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/Node.js
6802
6802
  var require_Node = __commonJS(function(exports) {
6803
6803
  var applyReviver = require_applyReviver();
6804
6804
  var identity = require_identity();
@@ -6835,7 +6835,7 @@ var require_Node = __commonJS(function(exports) {
6835
6835
  exports.NodeBase = NodeBase;
6836
6836
  });
6837
6837
 
6838
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Alias.js
6838
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/Alias.js
6839
6839
  var require_Alias = __commonJS(function(exports) {
6840
6840
  var anchors = require_anchors();
6841
6841
  var visit = require_visit();
@@ -6854,6 +6854,8 @@ var require_Alias = __commonJS(function(exports) {
6854
6854
  });
6855
6855
  }
6856
6856
  resolve(doc, ctx) {
6857
+ if (ctx?.maxAliasCount === 0)
6858
+ throw new ReferenceError("Alias resolution is disabled");
6857
6859
  let nodes;
6858
6860
  if (ctx?.aliasResolveCache) {
6859
6861
  nodes = ctx.aliasResolveCache;
@@ -6943,7 +6945,7 @@ var require_Alias = __commonJS(function(exports) {
6943
6945
  exports.Alias = Alias;
6944
6946
  });
6945
6947
 
6946
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Scalar.js
6948
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/Scalar.js
6947
6949
  var require_Scalar = __commonJS(function(exports) {
6948
6950
  var identity = require_identity();
6949
6951
  var Node = require_Node();
@@ -6971,7 +6973,7 @@ var require_Scalar = __commonJS(function(exports) {
6971
6973
  exports.isScalarValue = isScalarValue;
6972
6974
  });
6973
6975
 
6974
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/createNode.js
6976
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/doc/createNode.js
6975
6977
  var require_createNode = __commonJS(function(exports) {
6976
6978
  var Alias = require_Alias();
6977
6979
  var identity = require_identity();
@@ -7043,7 +7045,7 @@ var require_createNode = __commonJS(function(exports) {
7043
7045
  exports.createNode = createNode;
7044
7046
  });
7045
7047
 
7046
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Collection.js
7048
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/Collection.js
7047
7049
  var require_Collection = __commonJS(function(exports) {
7048
7050
  var createNode = require_createNode();
7049
7051
  var identity = require_identity();
@@ -7158,7 +7160,7 @@ var require_Collection = __commonJS(function(exports) {
7158
7160
  exports.isEmptyPath = isEmptyPath;
7159
7161
  });
7160
7162
 
7161
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyComment.js
7163
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyComment.js
7162
7164
  var require_stringifyComment = __commonJS(function(exports) {
7163
7165
  var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
7164
7166
  function indentComment(comment, indent) {
@@ -7175,7 +7177,7 @@ var require_stringifyComment = __commonJS(function(exports) {
7175
7177
  exports.stringifyComment = stringifyComment;
7176
7178
  });
7177
7179
 
7178
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/foldFlowLines.js
7180
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/foldFlowLines.js
7179
7181
  var require_foldFlowLines = __commonJS(function(exports) {
7180
7182
  var FOLD_FLOW = "flow";
7181
7183
  var FOLD_BLOCK = "block";
@@ -7312,7 +7314,7 @@ ${indent}${text.slice(fold + 1, end)}`;
7312
7314
  exports.foldFlowLines = foldFlowLines;
7313
7315
  });
7314
7316
 
7315
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyString.js
7317
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyString.js
7316
7318
  var require_stringifyString = __commonJS(function(exports) {
7317
7319
  var Scalar = require_Scalar();
7318
7320
  var foldFlowLines = require_foldFlowLines();
@@ -7610,7 +7612,7 @@ ${indent}`);
7610
7612
  exports.stringifyString = stringifyString;
7611
7613
  });
7612
7614
 
7613
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringify.js
7615
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringify.js
7614
7616
  var require_stringify = __commonJS(function(exports) {
7615
7617
  var anchors = require_anchors();
7616
7618
  var identity = require_identity();
@@ -7731,7 +7733,7 @@ ${ctx.indent}${str}`;
7731
7733
  exports.stringify = stringify;
7732
7734
  });
7733
7735
 
7734
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyPair.js
7736
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyPair.js
7735
7737
  var require_stringifyPair = __commonJS(function(exports) {
7736
7738
  var identity = require_identity();
7737
7739
  var Scalar = require_Scalar();
@@ -7867,7 +7869,7 @@ ${ctx.indent}`;
7867
7869
  exports.stringifyPair = stringifyPair;
7868
7870
  });
7869
7871
 
7870
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/log.js
7872
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/log.js
7871
7873
  var require_log = __commonJS(function(exports) {
7872
7874
  var node_process = __require("process");
7873
7875
  function debug(logLevel, ...messages) {
@@ -7886,7 +7888,7 @@ var require_log = __commonJS(function(exports) {
7886
7888
  exports.warn = warn;
7887
7889
  });
7888
7890
 
7889
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/merge.js
7891
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/merge.js
7890
7892
  var require_merge = __commonJS(function(exports) {
7891
7893
  var identity = require_identity();
7892
7894
  var Scalar = require_Scalar();
@@ -7903,18 +7905,18 @@ var require_merge = __commonJS(function(exports) {
7903
7905
  };
7904
7906
  var isMergeKey = (ctx, key) => (merge.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default);
7905
7907
  function addMergeToJSMap(ctx, map, value) {
7906
- value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
7907
- if (identity.isSeq(value))
7908
- for (const it of value.items)
7908
+ const source = resolveAliasValue(ctx, value);
7909
+ if (identity.isSeq(source))
7910
+ for (const it of source.items)
7909
7911
  mergeValue(ctx, map, it);
7910
- else if (Array.isArray(value))
7911
- for (const it of value)
7912
+ else if (Array.isArray(source))
7913
+ for (const it of source)
7912
7914
  mergeValue(ctx, map, it);
7913
7915
  else
7914
- mergeValue(ctx, map, value);
7916
+ mergeValue(ctx, map, source);
7915
7917
  }
7916
7918
  function mergeValue(ctx, map, value) {
7917
- const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
7919
+ const source = resolveAliasValue(ctx, value);
7918
7920
  if (!identity.isMap(source))
7919
7921
  throw new Error("Merge sources must be maps or map aliases");
7920
7922
  const srcMap = source.toJSON(null, ctx, Map);
@@ -7935,12 +7937,15 @@ var require_merge = __commonJS(function(exports) {
7935
7937
  }
7936
7938
  return map;
7937
7939
  }
7940
+ function resolveAliasValue(ctx, value) {
7941
+ return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
7942
+ }
7938
7943
  exports.addMergeToJSMap = addMergeToJSMap;
7939
7944
  exports.isMergeKey = isMergeKey;
7940
7945
  exports.merge = merge;
7941
7946
  });
7942
7947
 
7943
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js
7948
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/addPairToJSMap.js
7944
7949
  var require_addPairToJSMap = __commonJS(function(exports) {
7945
7950
  var log = require_log();
7946
7951
  var merge = require_merge();
@@ -8001,7 +8006,7 @@ var require_addPairToJSMap = __commonJS(function(exports) {
8001
8006
  exports.addPairToJSMap = addPairToJSMap;
8002
8007
  });
8003
8008
 
8004
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Pair.js
8009
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/Pair.js
8005
8010
  var require_Pair = __commonJS(function(exports) {
8006
8011
  var createNode = require_createNode();
8007
8012
  var stringifyPair = require_stringifyPair();
@@ -8039,7 +8044,7 @@ var require_Pair = __commonJS(function(exports) {
8039
8044
  exports.createPair = createPair;
8040
8045
  });
8041
8046
 
8042
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyCollection.js
8047
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyCollection.js
8043
8048
  var require_stringifyCollection = __commonJS(function(exports) {
8044
8049
  var identity = require_identity();
8045
8050
  var stringify = require_stringify();
@@ -8191,7 +8196,7 @@ ${indent}${end}`;
8191
8196
  exports.stringifyCollection = stringifyCollection;
8192
8197
  });
8193
8198
 
8194
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLMap.js
8199
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/YAMLMap.js
8195
8200
  var require_YAMLMap = __commonJS(function(exports) {
8196
8201
  var stringifyCollection = require_stringifyCollection();
8197
8202
  var addPairToJSMap = require_addPairToJSMap();
@@ -8318,7 +8323,7 @@ var require_YAMLMap = __commonJS(function(exports) {
8318
8323
  exports.findPair = findPair;
8319
8324
  });
8320
8325
 
8321
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/map.js
8326
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/common/map.js
8322
8327
  var require_map = __commonJS(function(exports) {
8323
8328
  var identity = require_identity();
8324
8329
  var YAMLMap = require_YAMLMap();
@@ -8337,7 +8342,7 @@ var require_map = __commonJS(function(exports) {
8337
8342
  exports.map = map;
8338
8343
  });
8339
8344
 
8340
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLSeq.js
8345
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/nodes/YAMLSeq.js
8341
8346
  var require_YAMLSeq = __commonJS(function(exports) {
8342
8347
  var createNode = require_createNode();
8343
8348
  var stringifyCollection = require_stringifyCollection();
@@ -8430,7 +8435,7 @@ var require_YAMLSeq = __commonJS(function(exports) {
8430
8435
  exports.YAMLSeq = YAMLSeq;
8431
8436
  });
8432
8437
 
8433
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/seq.js
8438
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/common/seq.js
8434
8439
  var require_seq = __commonJS(function(exports) {
8435
8440
  var identity = require_identity();
8436
8441
  var YAMLSeq = require_YAMLSeq();
@@ -8449,7 +8454,7 @@ var require_seq = __commonJS(function(exports) {
8449
8454
  exports.seq = seq;
8450
8455
  });
8451
8456
 
8452
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/string.js
8457
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/common/string.js
8453
8458
  var require_string = __commonJS(function(exports) {
8454
8459
  var stringifyString = require_stringifyString();
8455
8460
  var string = {
@@ -8465,7 +8470,7 @@ var require_string = __commonJS(function(exports) {
8465
8470
  exports.string = string;
8466
8471
  });
8467
8472
 
8468
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/null.js
8473
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/common/null.js
8469
8474
  var require_null = __commonJS(function(exports) {
8470
8475
  var Scalar = require_Scalar();
8471
8476
  var nullTag = {
@@ -8480,7 +8485,7 @@ var require_null = __commonJS(function(exports) {
8480
8485
  exports.nullTag = nullTag;
8481
8486
  });
8482
8487
 
8483
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/bool.js
8488
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/core/bool.js
8484
8489
  var require_bool = __commonJS(function(exports) {
8485
8490
  var Scalar = require_Scalar();
8486
8491
  var boolTag = {
@@ -8501,7 +8506,7 @@ var require_bool = __commonJS(function(exports) {
8501
8506
  exports.boolTag = boolTag;
8502
8507
  });
8503
8508
 
8504
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyNumber.js
8509
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyNumber.js
8505
8510
  var require_stringifyNumber = __commonJS(function(exports) {
8506
8511
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
8507
8512
  if (typeof value === "bigint")
@@ -8510,7 +8515,7 @@ var require_stringifyNumber = __commonJS(function(exports) {
8510
8515
  if (!isFinite(num))
8511
8516
  return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf";
8512
8517
  let n = Object.is(value, -0) ? "-0" : JSON.stringify(value);
8513
- if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^\d/.test(n)) {
8518
+ if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) {
8514
8519
  let i = n.indexOf(".");
8515
8520
  if (i < 0) {
8516
8521
  i = n.length;
@@ -8525,7 +8530,7 @@ var require_stringifyNumber = __commonJS(function(exports) {
8525
8530
  exports.stringifyNumber = stringifyNumber;
8526
8531
  });
8527
8532
 
8528
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/float.js
8533
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/core/float.js
8529
8534
  var require_float = __commonJS(function(exports) {
8530
8535
  var Scalar = require_Scalar();
8531
8536
  var stringifyNumber = require_stringifyNumber();
@@ -8568,7 +8573,7 @@ var require_float = __commonJS(function(exports) {
8568
8573
  exports.floatNaN = floatNaN;
8569
8574
  });
8570
8575
 
8571
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/int.js
8576
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/core/int.js
8572
8577
  var require_int = __commonJS(function(exports) {
8573
8578
  var stringifyNumber = require_stringifyNumber();
8574
8579
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -8610,7 +8615,7 @@ var require_int = __commonJS(function(exports) {
8610
8615
  exports.intOct = intOct;
8611
8616
  });
8612
8617
 
8613
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/schema.js
8618
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/core/schema.js
8614
8619
  var require_schema = __commonJS(function(exports) {
8615
8620
  var map = require_map();
8616
8621
  var _null = require_null();
@@ -8635,7 +8640,7 @@ var require_schema = __commonJS(function(exports) {
8635
8640
  exports.schema = schema;
8636
8641
  });
8637
8642
 
8638
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/json/schema.js
8643
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/json/schema.js
8639
8644
  var require_schema2 = __commonJS(function(exports) {
8640
8645
  var Scalar = require_Scalar();
8641
8646
  var map = require_map();
@@ -8699,7 +8704,7 @@ var require_schema2 = __commonJS(function(exports) {
8699
8704
  exports.schema = schema;
8700
8705
  });
8701
8706
 
8702
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/binary.js
8707
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/binary.js
8703
8708
  var require_binary = __commonJS(function(exports) {
8704
8709
  var node_buffer = __require("buffer");
8705
8710
  var Scalar = require_Scalar();
@@ -8754,7 +8759,7 @@ var require_binary = __commonJS(function(exports) {
8754
8759
  exports.binary = binary;
8755
8760
  });
8756
8761
 
8757
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
8762
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
8758
8763
  var require_pairs = __commonJS(function(exports) {
8759
8764
  var identity = require_identity();
8760
8765
  var Pair = require_Pair();
@@ -8829,7 +8834,7 @@ ${cn.comment}` : item.comment;
8829
8834
  exports.resolvePairs = resolvePairs;
8830
8835
  });
8831
8836
 
8832
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/omap.js
8837
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/omap.js
8833
8838
  var require_omap = __commonJS(function(exports) {
8834
8839
  var identity = require_identity();
8835
8840
  var toJS = require_toJS();
@@ -8901,7 +8906,7 @@ var require_omap = __commonJS(function(exports) {
8901
8906
  exports.omap = omap;
8902
8907
  });
8903
8908
 
8904
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/bool.js
8909
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/bool.js
8905
8910
  var require_bool2 = __commonJS(function(exports) {
8906
8911
  var Scalar = require_Scalar();
8907
8912
  function boolStringify({ value, source }, ctx) {
@@ -8930,7 +8935,7 @@ var require_bool2 = __commonJS(function(exports) {
8930
8935
  exports.trueTag = trueTag;
8931
8936
  });
8932
8937
 
8933
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/float.js
8938
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/float.js
8934
8939
  var require_float2 = __commonJS(function(exports) {
8935
8940
  var Scalar = require_Scalar();
8936
8941
  var stringifyNumber = require_stringifyNumber();
@@ -8976,7 +8981,7 @@ var require_float2 = __commonJS(function(exports) {
8976
8981
  exports.floatNaN = floatNaN;
8977
8982
  });
8978
8983
 
8979
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/int.js
8984
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/int.js
8980
8985
  var require_int2 = __commonJS(function(exports) {
8981
8986
  var stringifyNumber = require_stringifyNumber();
8982
8987
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -9052,7 +9057,7 @@ var require_int2 = __commonJS(function(exports) {
9052
9057
  exports.intOct = intOct;
9053
9058
  });
9054
9059
 
9055
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/set.js
9060
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/set.js
9056
9061
  var require_set = __commonJS(function(exports) {
9057
9062
  var identity = require_identity();
9058
9063
  var Pair = require_Pair();
@@ -9135,7 +9140,7 @@ var require_set = __commonJS(function(exports) {
9135
9140
  exports.set = set;
9136
9141
  });
9137
9142
 
9138
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
9143
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
9139
9144
  var require_timestamp = __commonJS(function(exports) {
9140
9145
  var stringifyNumber = require_stringifyNumber();
9141
9146
  function parseSexagesimal(str, asBigInt) {
@@ -9217,7 +9222,7 @@ var require_timestamp = __commonJS(function(exports) {
9217
9222
  exports.timestamp = timestamp;
9218
9223
  });
9219
9224
 
9220
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/schema.js
9225
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/yaml-1.1/schema.js
9221
9226
  var require_schema3 = __commonJS(function(exports) {
9222
9227
  var map = require_map();
9223
9228
  var _null = require_null();
@@ -9258,7 +9263,7 @@ var require_schema3 = __commonJS(function(exports) {
9258
9263
  exports.schema = schema;
9259
9264
  });
9260
9265
 
9261
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/tags.js
9266
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/tags.js
9262
9267
  var require_tags = __commonJS(function(exports) {
9263
9268
  var map = require_map();
9264
9269
  var _null = require_null();
@@ -9349,7 +9354,7 @@ var require_tags = __commonJS(function(exports) {
9349
9354
  exports.getTags = getTags;
9350
9355
  });
9351
9356
 
9352
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/Schema.js
9357
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/schema/Schema.js
9353
9358
  var require_Schema = __commonJS(function(exports) {
9354
9359
  var identity = require_identity();
9355
9360
  var map = require_map();
@@ -9379,7 +9384,7 @@ var require_Schema = __commonJS(function(exports) {
9379
9384
  exports.Schema = Schema;
9380
9385
  });
9381
9386
 
9382
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyDocument.js
9387
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/stringify/stringifyDocument.js
9383
9388
  var require_stringifyDocument = __commonJS(function(exports) {
9384
9389
  var identity = require_identity();
9385
9390
  var stringify = require_stringify();
@@ -9459,7 +9464,7 @@ var require_stringifyDocument = __commonJS(function(exports) {
9459
9464
  exports.stringifyDocument = stringifyDocument;
9460
9465
  });
9461
9466
 
9462
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/Document.js
9467
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/doc/Document.js
9463
9468
  var require_Document = __commonJS(function(exports) {
9464
9469
  var Alias = require_Alias();
9465
9470
  var Collection = require_Collection();
@@ -9694,7 +9699,7 @@ var require_Document = __commonJS(function(exports) {
9694
9699
  exports.Document = Document;
9695
9700
  });
9696
9701
 
9697
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/errors.js
9702
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/errors.js
9698
9703
  var require_errors2 = __commonJS(function(exports) {
9699
9704
  class YAMLError extends Error {
9700
9705
  constructor(name, pos, code, message) {
@@ -9759,7 +9764,7 @@ ${pointer}
9759
9764
  exports.prettifyError = prettifyError;
9760
9765
  });
9761
9766
 
9762
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-props.js
9767
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-props.js
9763
9768
  var require_resolve_props = __commonJS(function(exports) {
9764
9769
  function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
9765
9770
  let spaceBefore = false;
@@ -9889,7 +9894,7 @@ var require_resolve_props = __commonJS(function(exports) {
9889
9894
  exports.resolveProps = resolveProps;
9890
9895
  });
9891
9896
 
9892
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-contains-newline.js
9897
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/util-contains-newline.js
9893
9898
  var require_util_contains_newline = __commonJS(function(exports) {
9894
9899
  function containsNewline(key) {
9895
9900
  if (!key)
@@ -9929,7 +9934,7 @@ var require_util_contains_newline = __commonJS(function(exports) {
9929
9934
  exports.containsNewline = containsNewline;
9930
9935
  });
9931
9936
 
9932
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-flow-indent-check.js
9937
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/util-flow-indent-check.js
9933
9938
  var require_util_flow_indent_check = __commonJS(function(exports) {
9934
9939
  var utilContainsNewline = require_util_contains_newline();
9935
9940
  function flowIndentCheck(indent, fc, onError) {
@@ -9944,7 +9949,7 @@ var require_util_flow_indent_check = __commonJS(function(exports) {
9944
9949
  exports.flowIndentCheck = flowIndentCheck;
9945
9950
  });
9946
9951
 
9947
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-map-includes.js
9952
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/util-map-includes.js
9948
9953
  var require_util_map_includes = __commonJS(function(exports) {
9949
9954
  var identity = require_identity();
9950
9955
  function mapIncludes(ctx, items, search) {
@@ -9957,7 +9962,7 @@ var require_util_map_includes = __commonJS(function(exports) {
9957
9962
  exports.mapIncludes = mapIncludes;
9958
9963
  });
9959
9964
 
9960
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-map.js
9965
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-block-map.js
9961
9966
  var require_resolve_block_map = __commonJS(function(exports) {
9962
9967
  var Pair = require_Pair();
9963
9968
  var YAMLMap = require_YAMLMap();
@@ -10064,7 +10069,7 @@ var require_resolve_block_map = __commonJS(function(exports) {
10064
10069
  exports.resolveBlockMap = resolveBlockMap;
10065
10070
  });
10066
10071
 
10067
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-seq.js
10072
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-block-seq.js
10068
10073
  var require_resolve_block_seq = __commonJS(function(exports) {
10069
10074
  var YAMLSeq = require_YAMLSeq();
10070
10075
  var resolveProps = require_resolve_props();
@@ -10112,7 +10117,7 @@ var require_resolve_block_seq = __commonJS(function(exports) {
10112
10117
  exports.resolveBlockSeq = resolveBlockSeq;
10113
10118
  });
10114
10119
 
10115
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-end.js
10120
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-end.js
10116
10121
  var require_resolve_end = __commonJS(function(exports) {
10117
10122
  function resolveEnd(end, offset, reqSpace, onError) {
10118
10123
  let comment = "";
@@ -10152,7 +10157,7 @@ var require_resolve_end = __commonJS(function(exports) {
10152
10157
  exports.resolveEnd = resolveEnd;
10153
10158
  });
10154
10159
 
10155
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-collection.js
10160
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-flow-collection.js
10156
10161
  var require_resolve_flow_collection = __commonJS(function(exports) {
10157
10162
  var identity = require_identity();
10158
10163
  var Pair = require_Pair();
@@ -10343,7 +10348,7 @@ var require_resolve_flow_collection = __commonJS(function(exports) {
10343
10348
  exports.resolveFlowCollection = resolveFlowCollection;
10344
10349
  });
10345
10350
 
10346
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-collection.js
10351
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/compose-collection.js
10347
10352
  var require_compose_collection = __commonJS(function(exports) {
10348
10353
  var identity = require_identity();
10349
10354
  var Scalar = require_Scalar();
@@ -10405,7 +10410,7 @@ var require_compose_collection = __commonJS(function(exports) {
10405
10410
  exports.composeCollection = composeCollection;
10406
10411
  });
10407
10412
 
10408
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-scalar.js
10413
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-block-scalar.js
10409
10414
  var require_resolve_block_scalar = __commonJS(function(exports) {
10410
10415
  var Scalar = require_Scalar();
10411
10416
  function resolveBlockScalar(ctx, scalar, onError) {
@@ -10598,7 +10603,7 @@ var require_resolve_block_scalar = __commonJS(function(exports) {
10598
10603
  exports.resolveBlockScalar = resolveBlockScalar;
10599
10604
  });
10600
10605
 
10601
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-scalar.js
10606
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/resolve-flow-scalar.js
10602
10607
  var require_resolve_flow_scalar = __commonJS(function(exports) {
10603
10608
  var Scalar = require_Scalar();
10604
10609
  var resolveEnd = require_resolve_end();
@@ -10735,7 +10740,7 @@ var require_resolve_flow_scalar = __commonJS(function(exports) {
10735
10740
  while (next === " " || next === "\t")
10736
10741
  next = source[++i + 1];
10737
10742
  } else if (next === "x" || next === "u" || next === "U") {
10738
- const length = { x: 2, u: 4, U: 8 }[next];
10743
+ const length = next === "x" ? 2 : next === "u" ? 4 : 8;
10739
10744
  res += parseCharCode(source, i + 1, length, onError);
10740
10745
  i += length;
10741
10746
  } else {
@@ -10804,17 +10809,18 @@ var require_resolve_flow_scalar = __commonJS(function(exports) {
10804
10809
  const cc = source.substr(offset, length);
10805
10810
  const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
10806
10811
  const code = ok ? parseInt(cc, 16) : NaN;
10807
- if (isNaN(code)) {
10812
+ try {
10813
+ return String.fromCodePoint(code);
10814
+ } catch {
10808
10815
  const raw = source.substr(offset - 2, length + 2);
10809
10816
  onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`);
10810
10817
  return raw;
10811
10818
  }
10812
- return String.fromCodePoint(code);
10813
10819
  }
10814
10820
  exports.resolveFlowScalar = resolveFlowScalar;
10815
10821
  });
10816
10822
 
10817
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-scalar.js
10823
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/compose-scalar.js
10818
10824
  var require_compose_scalar = __commonJS(function(exports) {
10819
10825
  var identity = require_identity();
10820
10826
  var Scalar = require_Scalar();
@@ -10892,7 +10898,7 @@ var require_compose_scalar = __commonJS(function(exports) {
10892
10898
  exports.composeScalar = composeScalar;
10893
10899
  });
10894
10900
 
10895
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-empty-scalar-position.js
10901
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/util-empty-scalar-position.js
10896
10902
  var require_util_empty_scalar_position = __commonJS(function(exports) {
10897
10903
  function emptyScalarPosition(offset, before, pos) {
10898
10904
  if (before) {
@@ -10919,7 +10925,7 @@ var require_util_empty_scalar_position = __commonJS(function(exports) {
10919
10925
  exports.emptyScalarPosition = emptyScalarPosition;
10920
10926
  });
10921
10927
 
10922
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-node.js
10928
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/compose-node.js
10923
10929
  var require_compose_node = __commonJS(function(exports) {
10924
10930
  var Alias = require_Alias();
10925
10931
  var identity = require_identity();
@@ -11022,7 +11028,7 @@ var require_compose_node = __commonJS(function(exports) {
11022
11028
  exports.composeNode = composeNode;
11023
11029
  });
11024
11030
 
11025
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-doc.js
11031
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/compose-doc.js
11026
11032
  var require_compose_doc = __commonJS(function(exports) {
11027
11033
  var Document = require_Document();
11028
11034
  var composeNode = require_compose_node();
@@ -11062,7 +11068,7 @@ var require_compose_doc = __commonJS(function(exports) {
11062
11068
  exports.composeDoc = composeDoc;
11063
11069
  });
11064
11070
 
11065
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/composer.js
11071
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/compose/composer.js
11066
11072
  var require_composer = __commonJS(function(exports) {
11067
11073
  var node_process = __require("process");
11068
11074
  var directives = require_directives();
@@ -11251,7 +11257,7 @@ ${end.comment}` : end.comment;
11251
11257
  exports.Composer = Composer;
11252
11258
  });
11253
11259
 
11254
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-scalar.js
11260
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/cst-scalar.js
11255
11261
  var require_cst_scalar = __commonJS(function(exports) {
11256
11262
  var resolveBlockScalar = require_resolve_block_scalar();
11257
11263
  var resolveFlowScalar = require_resolve_flow_scalar();
@@ -11441,7 +11447,7 @@ var require_cst_scalar = __commonJS(function(exports) {
11441
11447
  exports.setScalarValue = setScalarValue;
11442
11448
  });
11443
11449
 
11444
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-stringify.js
11450
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/cst-stringify.js
11445
11451
  var require_cst_stringify = __commonJS(function(exports) {
11446
11452
  var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
11447
11453
  function stringifyToken(token) {
@@ -11499,7 +11505,7 @@ var require_cst_stringify = __commonJS(function(exports) {
11499
11505
  exports.stringify = stringify;
11500
11506
  });
11501
11507
 
11502
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-visit.js
11508
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/cst-visit.js
11503
11509
  var require_cst_visit = __commonJS(function(exports) {
11504
11510
  var BREAK = Symbol("break visit");
11505
11511
  var SKIP = Symbol("skip children");
@@ -11558,7 +11564,7 @@ var require_cst_visit = __commonJS(function(exports) {
11558
11564
  exports.visit = visit;
11559
11565
  });
11560
11566
 
11561
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst.js
11567
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/cst.js
11562
11568
  var require_cst = __commonJS(function(exports) {
11563
11569
  var cstScalar = require_cst_scalar();
11564
11570
  var cstStringify = require_cst_stringify();
@@ -11659,7 +11665,7 @@ var require_cst = __commonJS(function(exports) {
11659
11665
  exports.tokenType = tokenType;
11660
11666
  });
11661
11667
 
11662
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/lexer.js
11668
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/lexer.js
11663
11669
  var require_lexer = __commonJS(function(exports) {
11664
11670
  var cst = require_cst();
11665
11671
  function isEmpty(ch) {
@@ -12245,7 +12251,7 @@ var require_lexer = __commonJS(function(exports) {
12245
12251
  exports.Lexer = Lexer;
12246
12252
  });
12247
12253
 
12248
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/line-counter.js
12254
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/line-counter.js
12249
12255
  var require_line_counter = __commonJS(function(exports) {
12250
12256
  class LineCounter {
12251
12257
  constructor() {
@@ -12273,7 +12279,7 @@ var require_line_counter = __commonJS(function(exports) {
12273
12279
  exports.LineCounter = LineCounter;
12274
12280
  });
12275
12281
 
12276
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/parser.js
12282
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/parse/parser.js
12277
12283
  var require_parser = __commonJS(function(exports) {
12278
12284
  var node_process = __require("process");
12279
12285
  var cst = require_cst();
@@ -13122,7 +13128,7 @@ var require_parser = __commonJS(function(exports) {
13122
13128
  exports.Parser = Parser;
13123
13129
  });
13124
13130
 
13125
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/public-api.js
13131
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/public-api.js
13126
13132
  var require_public_api = __commonJS(function(exports) {
13127
13133
  var composer = require_composer();
13128
13134
  var Document = require_Document();
@@ -17851,6 +17857,9 @@ function contentTypeForContentExtension(extension) {
17851
17857
  // ../shared/src/constants/gm-engine.ts
17852
17858
  var GM_ENGINES = ["v1", "v2"];
17853
17859
 
17860
+ // ../shared/src/game-time.ts
17861
+ var TIME_TRACKER_KEY = "time";
17862
+
17854
17863
  // ../shared/src/platform-instructions.ts
17855
17864
  var PLATFORM_INSTRUCTIONS = [
17856
17865
  {
@@ -17864,53 +17873,65 @@ var PLATFORM_INSTRUCTIONS = [
17864
17873
  }
17865
17874
  },
17866
17875
  {
17867
- id: "a-good-turn",
17868
- revision: 2,
17876
+ id: "only-the-story",
17877
+ revision: 1,
17869
17878
  instruction: {
17870
- name: "A good turn",
17879
+ name: "Only the story",
17871
17880
  when: "always",
17872
17881
  section: "Narration",
17873
- body: "You are the game master, not a clerk: the tools keep the numbers, and the story is yours. Resolve what the player did, then let the world answer: the people present react from what they want. Build on what is already true before you invent: the scene's purpose, people here who have not spoken, threads left open."
17882
+ body: "Everything you write outside tool calls is the story the player reads. Never mention your tools, what you look up or record, the current state, trackers, or what you will roll or do next, and never step out of the story to speak as yourself unless the player asks you something out of character. Anything that is not story goes in a hidden note."
17874
17883
  }
17875
17884
  },
17876
17885
  {
17877
- id: "places-not-squares",
17886
+ id: "rolls",
17878
17887
  revision: 2,
17879
17888
  instruction: {
17880
- name: "Places, not squares",
17889
+ name: "Rolls",
17881
17890
  when: "always",
17882
- section: "Narration",
17883
- body: `Never write grid squares or coordinates in the story ("moves to G5", "the goblin at I14"): they are for the current state and your tool calls, and the map shows where everyone stands. Say where someone goes by what is there and how far: "walks over to the well", "ducks behind the overturned cart", "closes to within a sword's reach of the bandit".`
17891
+ section: "Rules",
17892
+ body: "When something attempted could fail, roll for it, against a difficulty taken from the files when they set one. The player's dice appear only when you call the tool for their roll: never ask the player to roll in your narration."
17884
17893
  }
17885
17894
  },
17886
17895
  {
17887
- id: "travel-times",
17896
+ id: "each-turn",
17888
17897
  revision: 1,
17889
17898
  instruction: {
17890
- name: "Journeys, not timetables",
17899
+ name: "Each turn",
17891
17900
  when: "always",
17892
17901
  section: "Narration",
17893
- body: `research() gives exact distances and travel times ("about 2.8 km north, ~35 min on foot"). They keep the world consistent and belong in your tool calls (how much time passes), not recited in the story: "You walk 35 minutes to the butcher's shop" reads like a timetable. Tell the way as the character lives it: "a short walk across the market square", "the sun is well up by the time you reach the butcher's", "half a day's ride along the coast road". Give a figure only when it matters to the moment (a deadline, a chase, a choice between routes) or the player asks, and then say it the way a person would: "a good half hour", "a couple of miles".`
17902
+ body: `Resolve what the player did, then let the people present react from what they want. End each reply on something concrete in front of the player's character that they must answer: someone speaks or acts to them, a threat moves, a choice with a cost. Never end on stillness, a summary or a bare "What do you do?".`
17894
17903
  }
17895
17904
  },
17896
17905
  {
17897
- id: "when-to-roll",
17898
- revision: 1,
17906
+ id: "places-not-squares",
17907
+ revision: 3,
17899
17908
  instruction: {
17900
- name: "When to roll",
17901
- when: "always",
17902
- section: "Rules",
17903
- body: "Call for a roll whenever something attempted could fail: what the player's character attempts, and what anyone else attempts that bears on them. Set the difficulty before the dice, and narrate the result honestly against it, failure included."
17909
+ name: "Places, not squares",
17910
+ when: "situational",
17911
+ trigger: "When someone moves within the scene, or a fight is on",
17912
+ section: "Narration",
17913
+ body: `Never write grid squares or coordinates in the story ("moves to G5", "the goblin at I14"): they are for the current state and your tool calls, and the map shows where everyone stands. Say where someone goes by what is there and how far: "walks over to the well", "ducks behind the overturned cart", "closes to within a sword's reach of the bandit".`
17914
+ }
17915
+ },
17916
+ {
17917
+ id: "travel-times",
17918
+ revision: 2,
17919
+ instruction: {
17920
+ name: "Journeys, not timetables",
17921
+ when: "situational",
17922
+ trigger: "When the party travels somewhere, or the player asks how far or how long a way is",
17923
+ section: "Narration",
17924
+ body: `research() gives exact distances and travel times ("about 2.8 km north, ~35 min on foot"). They keep the world consistent and belong in your tool calls (how much time passes), not recited in the story: "You walk 35 minutes to the butcher's shop" reads like a timetable. Tell the way as the character lives it: "a short walk across the market square", "the sun is well up by the time you reach the butcher's", "half a day's ride along the coast road". Give a figure only when it matters to the moment (a deadline, a chase, a choice between routes) or the player asks, and then say it the way a person would: "a good half hour", "a couple of miles".`
17904
17925
  }
17905
17926
  },
17906
17927
  {
17907
17928
  id: "combat-tracker",
17908
- revision: 6,
17929
+ revision: 8,
17909
17930
  instruction: {
17910
17931
  name: "Combat tracker",
17911
17932
  when: "always",
17912
17933
  section: "Rules",
17913
- body: `When the fight is over (every foe down, fled or surrendered, or the fighting stops), end it: "<its name>": "End".`,
17934
+ body: `read() the Running a fight procedure (<procedures>) as the fight starts, and run the fight by it. Everyone fighting, companions and allies included, has a place in the turn order. When the fight is over (every foe down, fled or surrendered, or the fighting stops), end it: {"id": "current-state", "op": "remove", "field": "trackers", "value": "Combat"}.`,
17914
17935
  tracker: {
17915
17936
  key: "fight",
17916
17937
  start: {
@@ -17926,18 +17947,18 @@ var PLATFORM_INSTRUCTIONS = [
17926
17947
  },
17927
17948
  starts: "when-started",
17928
17949
  trigger: "a fight begins",
17929
- when: "a fighter's turn ends, or someone joins or leaves the fight"
17950
+ when: "a fighter's turn ends"
17930
17951
  }
17931
17952
  }
17932
17953
  },
17933
17954
  {
17934
17955
  id: "objectives-tracker",
17935
- revision: 3,
17956
+ revision: 5,
17936
17957
  instruction: {
17937
17958
  name: "Objectives",
17938
17959
  when: "always",
17939
17960
  section: "Rules",
17940
- body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one when the story gives the party one (an order, a job or request taken on, a goal of their own that outlasts the scene), worded as the party knows it then, short, one goal to an objective: "Objectives": "Add: Find who opened the lid". Mark it done or failed when the story settles it: "Objectives": "Find who opened the lid: done". Never write a quest's later steps ahead of the story: they come as objectives of their own. One that runs over time (keep watch, log every contact) is done only when its end comes.`,
17961
+ body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one worded as the party knows it then, short, one step to an objective: {"id": "current-state", "op": "add", "field": "trackers[Objectives].items", "value": "Find Item A"}. A job with several steps they already know (go somewhere, learn something, report back) is one objective per step, in order, each an add of its own ("Get into Place A", "Find Item A", "Bring Item A to Character B"), never one item joined with "and" or "then", so each is marked done when it happens. Mark one done or failed when the story settles it: {"id": "current-state", "op": "set", "field": "trackers[Objectives].items[Find Item A]", "value": "done"}. Steps they do not know yet are never written ahead of the story: they come as objectives of their own when the story gives them. One that runs over time (keep watch, log every contact) is done only when its end comes.`,
17941
17962
  tracker: {
17942
17963
  key: "objectives",
17943
17964
  start: {
@@ -17951,19 +17972,19 @@ var PLATFORM_INSTRUCTIONS = [
17951
17972
  question: "In `earlier`, `player_turn` or `narration`, has the party been given an objective, something they are now working toward beyond this scene, that {tracker} does not hold under any wording?",
17952
17973
  yes: 'Yes: an order or an assignment handed to them (they act on it or do not refuse it), a request or a job they take on, or a goal they set themselves that will outlast this scene ("Find what woke the Sleeper", "Get the ledger to the Abbot", "Learn who signed the panel"), and nothing in {tracker} says it.',
17953
17974
  no: "No: {tracker} already holds it, however worded; or it is only floated, refused or still being weighed; or it is only what this scene is for, the next move (go to the archives, ask the clerk, cross the yard), or something done in the same moment.",
17954
- remind: `the story gave the party an objective the current state's Objectives list does not hold (add it as the party knows it now, short, one goal to an objective: "Objectives": "Add: <the objective>")`
17975
+ remind: `The story gave the party an objective the current state's Objectives list does not hold: add it as the party knows it now, short, one step to an objective, {"id": "current-state", "op": "add", "field": "trackers[Objectives].items", "value": "<the objective>"}`
17955
17976
  }
17956
17977
  }
17957
17978
  }
17958
17979
  },
17959
17980
  {
17960
17981
  id: "party-tracker",
17961
- revision: 3,
17982
+ revision: 4,
17962
17983
  instruction: {
17963
17984
  name: "Party",
17964
17985
  when: "always",
17965
17986
  section: "Rules",
17966
- body: `The Party is everyone who travels and acts with the player's character as one of them: the player's character, who is always in it, and the companions who have joined. Add someone when they join ("Party": "Add Mira"), and take them off when they leave, part ways for good or die ("Party": "Remove Mira"). Someone who only helps for a while (a guide for the afternoon, a guard who fights beside them once) is not in it unless the story has them join.`,
17987
+ body: `The Party is everyone who travels and acts with the player's character as one of them: the player's character, who is always in it, and the companions who have joined. Add someone when they join, {"id": "current-state", "op": "add", "field": "trackers[Party].records", "value": "Character B"}, and remove them the same way when they leave, part ways for good or die. Someone who only helps for a while (a guide for the afternoon, a guard who fights beside them once) is not in it unless the story has them join.`,
17967
17988
  tracker: {
17968
17989
  key: "party",
17969
17990
  start: {
@@ -17984,61 +18005,19 @@ var PLATFORM_INSTRUCTIONS = [
17984
18005
  question: "In `earlier`, `player_turn` or `narration`, has someone joined the party (now travelling and acting with the player's character as one of them) or left it, that {tracker} does not show?",
17985
18006
  yes: `Yes: someone agrees to come along, is taken on, or sets out with them as one of the group ("I'll come with you", hired for the journey, rescued and staying with them); or someone {tracker} lists leaves, parts ways for good, is left behind or dies.`,
17986
18007
  no: "No: {tracker} already shows who is in the party; or someone only helps for a moment, offers to come but has not joined, or is only met, fought beside or travelled near.",
17987
- remind: `someone joined or left the party and the current state's Party does not show it (add or take them off: "Party": "Add <name>", "Party": "Remove <name>")`
18008
+ remind: `Someone joined or left the party and the current state's Party does not show it: add or remove them, {"id": "current-state", "op": "add", "field": "trackers[Party].records", "value": "<their name>"}`
17988
18009
  }
17989
18010
  }
17990
18011
  }
17991
18012
  },
17992
- {
17993
- id: "death-saves-tracker",
17994
- revision: 3,
17995
- instruction: {
17996
- name: "Death saves",
17997
- when: "always",
17998
- section: "Rules",
17999
- body: `Only for a world whose rules have the dying roll to stay alive. Each save is a roll on their turn: the player's for their character, yours for anyone else. Record each one as it lands ("Death saves for Sara": "Successes +1", "Death saves for Sara": "Failures +1"). When they stabilize, are healed or die, end it ("Death saves for Sara": "End").`,
18000
- tracker: {
18001
- key: "death-saves",
18002
- start: {
18003
- name: "Death saves",
18004
- fields: [
18005
- {
18006
- kind: "count",
18007
- label: "Successes",
18008
- value: 0,
18009
- min: 0,
18010
- max: 3,
18011
- tone: "good",
18012
- display: "pips",
18013
- at: { "3": "Stable" }
18014
- },
18015
- {
18016
- kind: "count",
18017
- label: "Failures",
18018
- value: 0,
18019
- min: 0,
18020
- max: 3,
18021
- tone: "bad",
18022
- display: "pips",
18023
- at: { "3": "Dies" }
18024
- }
18025
- ]
18026
- },
18027
- starts: "when-started",
18028
- trigger: "someone falls dying, and the world's rules have them roll to stay alive",
18029
- perPerson: true,
18030
- when: "they make a death save"
18031
- }
18032
- }
18033
- },
18034
18013
  {
18035
18014
  id: "time-tracker",
18036
- revision: 4,
18015
+ revision: 6,
18037
18016
  instruction: {
18038
18017
  name: "Time",
18039
18018
  when: "always",
18040
18019
  section: "Rules",
18041
- body: `The Time tracker is the in-game time, and only you move it: nothing moves it after the turn. It is always a time someone could tell by a clock or the sky, in the world's own terms: a clock hour ("10:40 pm"), a part of the day ("dawn", "late afternoon") or how long until something ("about 2 hours until sunset"); never only how long passed ("30 minutes later": later than what?). Whenever the story takes time worth counting (a journey, a rest, a wait, a long job or talk), set it to the time it is now in the update() for that stretch of story, beside its other changes: "Time": "It is now dusk". When it shows a clock hour, a span is enough ("Time": "40 minutes pass") and the clock adds it. With nothing else to record, the time is still an update() of its own. A line or two of talk needs no record. Tell it once, as the time it is now: "It is now 12:10 am", "It is now dawn", "It is now about an hour until sunset"; never "a while later" or "30 minutes later". A rest recorded with its length ("scene": "The party rests for 1 hour") already moves the clock, so no "Time": "1 hour passes" after it. Narrate the hour, the light and what is open from the current state's time, never from a guess.`,
18020
+ body: `The in-game time is the current state's "time", and only you move it: nothing moves it after the turn. Whenever the story takes time worth counting, move it in the update() for that stretch of story, beside its other changes. With nothing else to record, the time is still an update() of its own. A line or two of talk needs no record. Tell it in the story once, as the time it is now ("It is now dusk"), never "a while later". Narrate the hour, the light and what is open from the current state's time, never from a guess.`,
18042
18021
  tracker: {
18043
18022
  key: "time",
18044
18023
  start: {
@@ -18053,29 +18032,38 @@ var PLATFORM_INSTRUCTIONS = [
18053
18032
  },
18054
18033
  {
18055
18034
  id: "player-moves",
18056
- revision: 2,
18035
+ revision: 5,
18057
18036
  instruction: {
18058
18037
  name: "Player moves",
18059
18038
  when: "situational",
18060
18039
  trigger: "When the player's character moves: walks somewhere, closes in, backs off, takes cover or follows someone",
18061
18040
  section: "Rules",
18062
- body: `When the move is one they can make, update() the current state with where the player's character ends up first, before you write anything or make any other call ("Kael": "Backs 30 ft away from Lookout 1"). Then tell the turn from there: a move then a shot is update(), then the words, then askPlayerToRoll.`
18041
+ body: `When the move is one they can make, record where the player's character ends up with update(): {"id": "current-state", "op": "set", "field": "people[Character A].position", "value": "30 ft away from Lookout 1"}.`
18063
18042
  }
18064
18043
  },
18065
18044
  {
18066
- id: "filling-gaps",
18067
- revision: 1,
18045
+ id: "fight-procedure",
18046
+ revision: 5,
18068
18047
  instruction: {
18069
- name: "Filling gaps",
18070
- when: "always",
18071
- section: "World",
18072
- body: "When research() finds nothing concrete, decide it yourself: how people speak and react from who their files say they are; a small detail a game master decides (a price, a name, what a shop sells) you decide and say, with an update() when it is a value a file keeps (a shop's prices, an innkeeper's name). Only lore, rules and history stay vague. When a place or person the player names has no file, take the nearest real one and play there. When research() says no file is called a name, nobody by that name is in this world: no character speaks of them as someone they know. Take the near match it offers when the player plainly means it, else let the story show nobody knows the name."
18048
+ name: "Running a fight",
18049
+ when: "procedure",
18050
+ trigger: "When a fight starts, and on its turns while it runs",
18051
+ pruning: "on-compaction",
18052
+ section: "Rules",
18053
+ body: `A fight runs on its turn order (the Trackers procedure says how a turn order works). Start it as the fight starts, with everyone taking part, as its line under "Trackers this game keeps" shows, and give each NPC in it a side if theirs is not set (People on the map). Pass the turn as each one ends, and play each turn as the fighter the turn order gives it to.
18054
+
18055
+ Turns. Every NPC's turn is yours to play, an ally's too (a companion travelling with the party): their move, their roll, what it did, then on to the next. Stop for the player only on the player's character's turn.
18056
+
18057
+ Reach. A melee attack needs its target within reach, so close the distance first, as a move.
18058
+
18059
+ Stats. As the fight starts, show the stat it turns on under the tokens (hit points as a bar, hidden for enemies), and give each extra in the fight that stat as a detail (People on the map says how). Work out who goes down from the numbers.
18060
+
18061
+ Table talk. Talk at the table as a game master running a fight would, as much as the moment needs: hand the turn over ("Character B's up.", "Character A, it's your turn. What do you do?"), say how a roll went and what it did ("Seventeen: that hits, for seven damage."), give the logistics a choice turns on (who is in reach, how far, who is flanked, what a move would provoke), and say how hurt someone looks ("he's barely standing") rather than their hit points left. Table talk is not narrating your process; your own bookkeeping stays unsaid.`
18073
18062
  }
18074
18063
  }
18075
18064
  ];
18076
18065
  var RESPONSE_LENGTH = "Response length";
18077
18066
  var LONG_TRAVEL = "Long-distance travel";
18078
- var STORY_PACING_KEY = "platform-story-pacing";
18079
18067
  var PLATFORM_OPTIONS = [
18080
18068
  {
18081
18069
  revision: 3,
@@ -18129,58 +18117,7 @@ var PLATFORM_OPTIONS = [
18129
18117
  }
18130
18118
  },
18131
18119
  {
18132
- revision: 1,
18133
- option: {
18134
- key: STORY_PACING_KEY,
18135
- label: "Story pacing",
18136
- description: "How hard the Game Master pushes the story forward.",
18137
- kind: "one",
18138
- default: ["driven"],
18139
- choices: [
18140
- {
18141
- key: "driven",
18142
- label: "Driven",
18143
- description: "Something moves every turn.",
18144
- instructions: [
18145
- {
18146
- when: "always",
18147
- body: `Each turn, something moves on besides what the player did: a clock, a threat, a rumour, someone who has been waiting.
18148
-
18149
- End each reply on something concrete in front of the player's character that they must answer: someone speaks or acts to them, an offer, a threat that moves, a choice with a cost, time running short. Never end on stillness ("the room is quiet"), a summary, or a bare "What do you do?".`
18150
- }
18151
- ]
18152
- },
18153
- {
18154
- key: "player-lead",
18155
- label: "Player Lead",
18156
- description: "Pushes when you push, lingers when you linger.",
18157
- instructions: [
18158
- {
18159
- when: "always",
18160
- body: `Match the pace to the player's. When they act, press on: consequences land and the people present push back. When they talk, look around or take their time, stay in the moment with them and bring in nothing new unless a thread already in motion arrives.
18161
-
18162
- End each reply on something in front of the player's character to answer: someone's reply or reaction, an invitation, a choice. Never end on stillness ("the room is quiet"), a summary, or a bare "What do you do?".`
18163
- }
18164
- ]
18165
- },
18166
- {
18167
- key: "slice-of-life",
18168
- label: "Slice of life",
18169
- description: "Time to talk and explore; no trouble unless you look for it.",
18170
- instructions: [
18171
- {
18172
- when: "always",
18173
- body: `Let scenes breathe. Spend the reply on the people and the place: conversation, small gestures, how somewhere looks, sounds and smells. Bring in no new threat, deadline or twist: trouble comes only when the player goes looking for it, or from something the story already set in motion.
18174
-
18175
- End each reply on something open in front of the player's character, however quiet the moment: someone speaks to them or catches their eye, an invitation, a small detail worth a closer look. Never end on stillness ("there is room to eat in peace"), a summary, or a bare "What do you do?".`
18176
- }
18177
- ]
18178
- }
18179
- ]
18180
- }
18181
- },
18182
- {
18183
- revision: 1,
18120
+ revision: 2,
18184
18121
  option: {
18185
18122
  key: "platform-long-distance-travel",
18186
18123
  label: LONG_TRAVEL,
@@ -18201,7 +18138,7 @@ End each reply on something open in front of the player's character, however qui
18201
18138
  instructions: [
18202
18139
  {
18203
18140
  when: "always",
18204
- body: `When the party travels somewhere far (a trip of hours or more), take them straight there in the same reply: record it as arriving ("Move Kael and Mira to Harrowmere"), never "set out for", sum up the trip in a sentence or two (how long it took, how the way went), and go on from the arrival. When the player set out from the map, tell the arrival the same way.`
18141
+ body: "When the party travels somewhere far (a trip of hours or more), take them straight there in the same reply: set place to where they are going, never travel, and inc time by how long the trip took; sum up the trip in a sentence or two (how long it took, how the way went), and go on from the arrival. When the player set out from the map, tell the arrival the same way."
18205
18142
  }
18206
18143
  ]
18207
18144
  },
@@ -18212,7 +18149,7 @@ End each reply on something open in front of the player's character, however qui
18212
18149
  instructions: [
18213
18150
  {
18214
18151
  when: "always",
18215
- body: `When the party travels somewhere far (a trip of hours or more), play it as a journey to where the player said they are going. Set out first ("Kael and Mira set out for Harrowmere by the coast road; it takes about three days"), then tell the way in stages over several replies, for as long as the distance really takes: the road and the weather, rests and camps, people and trouble met on the way, with time passing as it would ("It is now dusk"). The party stays on the way the whole time: a camp or a rest is told with the time it takes, never a move to a camp or a stretch of road. Someone met stops the journey where they meet; when the party goes on from there, record it as "Kael and Mira set out again for Harrowmere", never as a move farther along the road. Keep heading for the place the player named unless they change course, and record the arrival ("Move Kael and Mira to Harrowmere") only once the way has been travelled. When the player set out from the map, they are already on the way: go on with the journey.`
18152
+ body: `When the party travels somewhere far (a trip of hours or more), play it as a journey to where the player said they are going. Set out first, setting travel to that place with the way and how long it takes ({"to": "place-b#2f4b9e10", "via": "the coast road", "takes": "about three days"}), then tell the way in stages over several replies, for as long as the distance really takes: the road and the weather, rests and camps, people and trouble met on the way, with the time moving as it would. The party stays on the road the whole time: a camp, a rest or someone met is told there, with the time it takes, never as a place of its own. Keep heading for the place the player named unless they change course, and set place to it only once the way has been travelled. When the player set out from the map, they are already on the way: go on with the journey.`
18216
18153
  }
18217
18154
  ]
18218
18155
  }
@@ -18236,9 +18173,6 @@ function sameJson(a, b) {
18236
18173
  return JSON.stringify(sortedKeys(a)) === JSON.stringify(sortedKeys(b));
18237
18174
  }
18238
18175
 
18239
- // ../shared/src/game-time.ts
18240
- var TIME_TRACKER_KEY = "time";
18241
-
18242
18176
  // ../shared/src/trackers.ts
18243
18177
  var TRACKER_TONES = ["good", "bad"];
18244
18178
  var TRACKER_ITEM_STATES = ["open", "done", "failed"];
@@ -18267,6 +18201,7 @@ var TRACKER_LIMITS = {
18267
18201
  };
18268
18202
  var labelSchema = string2().trim().max(TRACKER_LIMITS.label).nullish();
18269
18203
  var hiddenSchema = boolean2().nullish();
18204
+ var turnStampSchema = number2().int().nonnegative().nullish().catch(null);
18270
18205
  var refSchema = object({
18271
18206
  name: string2().trim().min(1).max(TRACKER_LIMITS.name),
18272
18207
  file: string2().nullish()
@@ -18395,7 +18330,9 @@ var trackerShapeSchema = object({
18395
18330
  lasts: _enum(TRACKER_LASTS).nullish(),
18396
18331
  from: string2().nullish(),
18397
18332
  since: string2().nullish(),
18398
- changed: string2().nullish()
18333
+ changed: string2().nullish(),
18334
+ sinceTurn: turnStampSchema,
18335
+ changedTurn: turnStampSchema
18399
18336
  });
18400
18337
  function isObject2(value) {
18401
18338
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -18440,7 +18377,13 @@ var TRACKER_DEFINITION_LIMITS = {
18440
18377
  };
18441
18378
  var trackerDefinitionSchema = object({
18442
18379
  key: string2().trim().min(1).max(TRACKER_DEFINITION_LIMITS.key).regex(/^[a-z0-9][a-z0-9-]*$/, "Keys are lowercase letters, digits and -"),
18443
- start: trackerShapeSchema.omit({ since: true, changed: true, from: true }),
18380
+ start: trackerShapeSchema.omit({
18381
+ since: true,
18382
+ changed: true,
18383
+ sinceTurn: true,
18384
+ changedTurn: true,
18385
+ from: true
18386
+ }),
18444
18387
  starts: _enum(TRACKER_STARTS),
18445
18388
  when: string2().trim().max(TRACKER_DEFINITION_LIMITS.when).nullish(),
18446
18389
  updates: _enum(TRACKER_UPDATES).nullish(),
@@ -18458,6 +18401,11 @@ var NUMBER_KINDS = new Set(["count", "clock", "scale"]);
18458
18401
  function isNumberField(field) {
18459
18402
  return NUMBER_KINDS.has(field.kind);
18460
18403
  }
18404
+ var QUOTED_PAIR = String.raw`"[^"\n]*"\s*:\s*"[^"\n]*"`;
18405
+ var QUOTED_ASIDE_RE = new RegExp(String.raw`\s*\([^()]*${QUOTED_PAIR}[^()]*\)`, "g");
18406
+ var QUOTED_PAIRS_RE = new RegExp(String.raw`:?\s*${QUOTED_PAIR}(?:\s*,\s*${QUOTED_PAIR})*`, "g");
18407
+ var EXAMPLE_PERSON = "Character A";
18408
+ var EXAMPLE_ORDER = { [EXAMPLE_PERSON]: 18, "Lookout 1": 12 };
18461
18409
  var ARTICLE_RE = /^(?:the|a|an)\s+/;
18462
18410
  var POSSESSIVE_RE = /['’]s\b/g;
18463
18411
  var NON_WORD_RE = /[^\p{L}\p{N}]+/gu;
@@ -18564,6 +18512,8 @@ function trackerDefinitionProblem(definition) {
18564
18512
  var GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH = 120;
18565
18513
  var GAME_OPTION_INSTRUCTION_BODY_MAX_LENGTH = 2000;
18566
18514
  var GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH = 1000;
18515
+ var GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH = 1e4;
18516
+ var GAME_OPTION_STORED_BODY_MAX_LENGTH = 12000;
18567
18517
  var GAME_OPTION_LABEL_MAX_LENGTH = 60;
18568
18518
  var GAME_OPTION_DESCRIPTION_MAX_LENGTH = 200;
18569
18519
  var GAME_OPTION_KEY_MAX_LENGTH = 60;
@@ -18573,16 +18523,26 @@ var GAME_OPTION_MAX_CHOICES = 8;
18573
18523
  var GAME_OPTIONS_MAX_INSTRUCTIONS = 200;
18574
18524
  var GAME_OPTIONS_MAX_ALWAYS_LENGTH = 1e4;
18575
18525
  var GAME_OPTIONS_MAX_SITUATIONAL = 100;
18576
- var GAME_OPTION_INSTRUCTION_WHEN = ["always", "situational"];
18526
+ var GAME_OPTIONS_MAX_PROCEDURES = 25;
18527
+ var GAME_OPTION_INSTRUCTION_WHEN = [
18528
+ "always",
18529
+ "situational",
18530
+ "procedure"
18531
+ ];
18532
+ var GAME_OPTION_PROCEDURE_PRUNING = [
18533
+ "after-use",
18534
+ "on-compaction"
18535
+ ];
18577
18536
  var GAME_OPTION_KINDS = ["one", "several", "toggle"];
18578
18537
  var keySchema = string2().trim().min(1).max(GAME_OPTION_KEY_MAX_LENGTH).regex(/^[a-z0-9][a-z0-9-]*$/, "Keys are lowercase letters, digits and -");
18579
18538
  var GAME_OPTION_SECTION_MAX_LENGTH = 40;
18580
18539
  var GAME_OPTIONS_MAX_SECTIONS = 30;
18581
18540
  var gameOptionInstructionSchema = object({
18582
18541
  name: string2().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
18583
- body: string2().trim().min(1).max(GAME_OPTION_INSTRUCTION_BODY_MAX_LENGTH),
18542
+ body: string2().trim().min(1).max(GAME_OPTION_STORED_BODY_MAX_LENGTH),
18584
18543
  when: _enum(GAME_OPTION_INSTRUCTION_WHEN),
18585
18544
  trigger: string2().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
18545
+ pruning: _enum(GAME_OPTION_PROCEDURE_PRUNING).optional(),
18586
18546
  section: string2().trim().max(GAME_OPTION_SECTION_MAX_LENGTH).optional(),
18587
18547
  tracker: trackerDefinitionSchema.optional(),
18588
18548
  platform: string2().max(GAME_OPTION_KEY_MAX_LENGTH).optional()
@@ -18655,7 +18615,28 @@ function sectionsOf(options) {
18655
18615
  ].map((name) => name.trim()).filter(Boolean);
18656
18616
  return [...new Set(names)];
18657
18617
  }
18658
- function instructionProblem(instruction, base) {
18618
+ function instructionBodyMax(instruction) {
18619
+ if (instruction.when === "procedure") {
18620
+ return GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH;
18621
+ }
18622
+ return instruction.when === "situational" && !instruction.tracker ? GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH : GAME_OPTION_INSTRUCTION_BODY_MAX_LENGTH;
18623
+ }
18624
+ function bodyLengthProblem(instruction) {
18625
+ const max = instructionBodyMax(instruction);
18626
+ const has = instruction.body.length;
18627
+ if (has <= max) {
18628
+ return null;
18629
+ }
18630
+ if (instruction.when === "procedure") {
18631
+ return `it is a procedure, so its text is at most ${max} characters (it has ${has}). Trim it, or split it into procedures for its parts.`;
18632
+ }
18633
+ if (instruction.when === "situational" && !instruction.tracker) {
18634
+ return `it is situational, so its text is at most ${max} characters (it has ${has}). Trim it, or make it a procedure: a procedure holds up to ${GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH} characters, read whole when its moment comes.`;
18635
+ }
18636
+ const kind = instruction.tracker ? "a tracker's rules" : "always on";
18637
+ return `it is ${kind}, so its text is at most ${max} characters (it has ${has}). Trim it, or make the part for one moment situational or a procedure.`;
18638
+ }
18639
+ function instructionProblem(instruction, base, bodyLengths) {
18659
18640
  if (instruction.tracker) {
18660
18641
  const problem = trackerDefinitionProblem(instruction.tracker);
18661
18642
  if (problem) {
@@ -18668,25 +18649,45 @@ function instructionProblem(instruction, base) {
18668
18649
  if (base && instruction.when === "always" && !instruction.name?.trim()) {
18669
18650
  return "an always instruction needs a title.";
18670
18651
  }
18671
- if (instruction.when === "situational" && instruction.body.length > GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH) {
18672
- return `"${instruction.name?.trim() || instructionTrigger(instruction)}" is situational, so its text is at most ${GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH} characters (it has ${instruction.body.length}). Split it or trim it.`;
18652
+ if (instruction.when === "procedure") {
18653
+ if (instruction.tracker) {
18654
+ return `"${instruction.name?.trim() || instructionTrigger(instruction)}" is a procedure, which can't define a tracker: make the tracker its own instruction.`;
18655
+ }
18656
+ if (!instruction.trigger?.trim()) {
18657
+ return "a procedure needs to say when to use it.";
18658
+ }
18659
+ if (base && !instruction.name?.trim()) {
18660
+ return "a procedure needs a title.";
18661
+ }
18662
+ }
18663
+ const long = bodyLengthProblem(instruction);
18664
+ if (bodyLengths && long) {
18665
+ return `"${instruction.name?.trim() || instructionTrigger(instruction)}": ${long}`;
18673
18666
  }
18674
18667
  return null;
18675
18668
  }
18676
- function gameOptionsProblem(options) {
18677
- const baseProblem = options.instructions.map((instruction) => instructionProblem(instruction, true)).find(Boolean);
18669
+ function gameOptionsProblem(options, { bodyLengths = true } = {}) {
18670
+ const baseProblem = options.instructions.map((instruction) => instructionProblem(instruction, true, bodyLengths)).find(Boolean);
18678
18671
  if (baseProblem) {
18679
18672
  return `In the base instructions, ${baseProblem}`;
18680
18673
  }
18681
18674
  for (const option of options.options) {
18682
18675
  for (const choice of option.choices) {
18683
- const problem = choice.instructions.map((instruction) => instructionProblem(instruction, false)).find(Boolean);
18676
+ const problem = choice.instructions.map((instruction) => instructionProblem(instruction, false, bodyLengths)).find(Boolean);
18684
18677
  if (problem) {
18685
18678
  return `In "${option.label} · ${choice.label}", ${problem}`;
18686
18679
  }
18687
18680
  }
18688
18681
  }
18689
- return trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
18682
+ return craftTimeProblem(options) ?? trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
18683
+ }
18684
+ var TIME_TRACKER_ID = "time-tracker";
18685
+ function craftTimeProblem(options) {
18686
+ const time = options.instructions.find((instruction) => instruction.platform === TIME_TRACKER_ID);
18687
+ if (!time || time.tracker?.key === TIME_TRACKER_KEY) {
18688
+ return null;
18689
+ }
18690
+ return `Craft's Time tracker ("${time.name}") must keep its tracker with the key "${TIME_TRACKER_KEY}": code moves the clock by that key. Reword its name, rules and trigger, or turn it off.`;
18690
18691
  }
18691
18692
  function trackerKeysProblem(options) {
18692
18693
  const lists = [
@@ -18754,14 +18755,67 @@ function gameOptionsLimitProblem(options) {
18754
18755
  if (situational > GAME_OPTIONS_MAX_SITUATIONAL) {
18755
18756
  return `One game can have up to ${situational} situational instructions; the limit is ${GAME_OPTIONS_MAX_SITUATIONAL}.`;
18756
18757
  }
18758
+ const procedures = worstCaseProcedureCount(options);
18759
+ if (procedures > GAME_OPTIONS_MAX_PROCEDURES) {
18760
+ return `One game can have up to ${procedures} procedures; the limit is ${GAME_OPTIONS_MAX_PROCEDURES}, Craft's included. Merge some, or make the short ones situational.`;
18761
+ }
18757
18762
  return null;
18758
18763
  }
18759
18764
  var gameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx) => {
18760
- const problem = gameOptionsProblem(withPlatform(options));
18765
+ for (const { path, instruction } of storedInstructions(options)) {
18766
+ const long = bodyLengthProblem(instruction);
18767
+ if (long) {
18768
+ ctx.addIssue({
18769
+ code: "custom",
18770
+ path: [...path, "body"],
18771
+ message: long
18772
+ });
18773
+ }
18774
+ }
18775
+ const problem = gameOptionsProblem(withPlatform(options), {
18776
+ bodyLengths: false
18777
+ });
18761
18778
  if (problem) {
18762
18779
  ctx.addIssue({ code: "custom", message: problem });
18763
18780
  }
18764
18781
  });
18782
+ function storedInstructions(options) {
18783
+ const found = [];
18784
+ const choicesOf = (path, choices) => {
18785
+ choices.forEach((choice, c) => {
18786
+ choice.instructions.forEach((instruction, i) => {
18787
+ found.push({
18788
+ path: [...path, "choices", c, "instructions", i],
18789
+ instruction
18790
+ });
18791
+ });
18792
+ });
18793
+ };
18794
+ options.instructions.forEach((instruction, i) => {
18795
+ if ("body" in instruction) {
18796
+ found.push({ path: ["instructions", i], instruction });
18797
+ }
18798
+ });
18799
+ options.options.forEach((option, o) => {
18800
+ if ("choices" in option) {
18801
+ choicesOf(["options", o], option.choices);
18802
+ }
18803
+ });
18804
+ for (const [id, override] of Object.entries(options.platform?.instructions ?? {})) {
18805
+ if ("body" in override) {
18806
+ found.push({
18807
+ path: ["platform", "instructions", id],
18808
+ instruction: override
18809
+ });
18810
+ }
18811
+ }
18812
+ for (const [key, override] of Object.entries(options.platform?.options ?? {})) {
18813
+ if ("choices" in override) {
18814
+ choicesOf(["platform", "options", key], override.choices);
18815
+ }
18816
+ }
18817
+ return found;
18818
+ }
18765
18819
  var storedGameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx) => {
18766
18820
  const problem = gameOptionsStructureProblem(withPlatform(options));
18767
18821
  if (problem) {
@@ -19058,18 +19112,26 @@ function worstCaseAlwaysLength(options) {
19058
19112
  function worstCaseSituationalCount(options) {
19059
19113
  return worstCase(options, situationalCount);
19060
19114
  }
19115
+ function procedureCount(instructions, _place) {
19116
+ return instructions.filter((instruction) => instruction.when === "procedure").length;
19117
+ }
19118
+ function worstCaseProcedureCount(options) {
19119
+ return worstCase(options, procedureCount);
19120
+ }
19061
19121
 
19062
19122
  // ../shared/src/gm-tool-settings.ts
19063
19123
  var gmToolSettingsInputSchema = object({
19064
19124
  askPlayerToRoll: boolean2().optional(),
19065
19125
  askUserQuestion: boolean2().optional(),
19066
19126
  generateNames: boolean2().optional(),
19127
+ gmExpressions: boolean2().optional(),
19067
19128
  research: boolean2().optional()
19068
19129
  }).strict();
19069
19130
  var gmToolSettingsSchema = object({
19070
19131
  askPlayerToRoll: boolean2(),
19071
19132
  askUserQuestion: boolean2(),
19072
19133
  generateNames: boolean2(),
19134
+ gmExpressions: boolean2(),
19073
19135
  research: boolean2()
19074
19136
  }).strict();
19075
19137
 
@@ -28150,6 +28212,9 @@ function sanitizeVfsPathForDisk(path) {
28150
28212
  return path.split("/").map((segment) => segment === "" ? "" : sanitizeVfsSegmentForDisk(segment)).join("/");
28151
28213
  }
28152
28214
 
28215
+ // ../shared/src/files/file-handle.ts
28216
+ var QUOTES = new Set(['"', "'", "“", "”", "‘", "’", "«", "»"]);
28217
+
28153
28218
  // ../shared/src/files/file-reference-content.ts
28154
28219
  var STATE_PATH_LEADING_SLASH_RE = /^\/+/;
28155
28220
  var STATE_PATH_ARRAY_INDEX_RE = /\[(?:\d+|\*)\]/g;
@@ -28373,13 +28438,13 @@ function referenceDisplayValue(value, qualify) {
28373
28438
  }
28374
28439
  return value;
28375
28440
  }
28376
- function projectFileContentReferencesForEditing(content, schema, layout) {
28377
- return projectValue(content, asSchemaNode(schema), referenceHintsFromLayout(layout), "");
28441
+ function projectFileContentReferencesForEditing(content, schema, layout, display) {
28442
+ return projectValue(content, asSchemaNode(schema), referenceHintsFromLayout(layout), "", display);
28378
28443
  }
28379
- function projectValue(value, schema, hints, path) {
28444
+ function projectValue(value, schema, hints, path, display) {
28380
28445
  const directTargets = directReferenceTargets(schema, hints, path);
28381
28446
  if (directTargets.length > 0) {
28382
- return referenceDisplayValue(value, directTargets.length > 1);
28447
+ return display?.(value) ?? referenceDisplayValue(value, directTargets.length > 1);
28383
28448
  }
28384
28449
  const itemSchema = schemaItems(schema);
28385
28450
  if (Array.isArray(value)) {
@@ -28387,9 +28452,9 @@ function projectValue(value, schema, hints, path) {
28387
28452
  const itemTargets = arrayItemReferenceTargets(schema, itemSchema, hints, path);
28388
28453
  if (itemTargets.length > 0) {
28389
28454
  const referenceSchema = referenceSchemaForTargets(itemSchema, itemTargets);
28390
- return value.map((item) => projectValue(item, referenceSchema, hints, itemPath));
28455
+ return value.map((item) => projectValue(item, referenceSchema, hints, itemPath, display));
28391
28456
  }
28392
- return itemSchema || hintHasPathPrefix(hints, itemPath) ? value.map((item) => projectValue(item, itemSchema, hints, itemPath)) : value;
28457
+ return itemSchema || hintHasPathPrefix(hints, itemPath) ? value.map((item) => projectValue(item, itemSchema, hints, itemPath, display)) : value;
28393
28458
  }
28394
28459
  const properties = schemaProperties2(schema);
28395
28460
  if (isRecord5(value) && (properties || hintHasPathPrefix(hints, path))) {
@@ -28401,7 +28466,7 @@ function projectValue(value, schema, hints, path) {
28401
28466
  if (!(childSchema || hintHasPathPrefix(hints, childPath))) {
28402
28467
  continue;
28403
28468
  }
28404
- const projected = projectValue(childValue, asSchemaNode(childSchema), hints, childPath);
28469
+ const projected = projectValue(childValue, asSchemaNode(childSchema), hints, childPath, display);
28405
28470
  if (projected !== childValue) {
28406
28471
  changed = true;
28407
28472
  next[key] = projected;
@@ -28621,6 +28686,14 @@ var fileContentBaseSchema = object({
28621
28686
  description: string2().optional(),
28622
28687
  image: preprocess((value) => value === null ? undefined : value, imageObjectSchema.optional())
28623
28688
  }).passthrough();
28689
+ var IMAGE_URL_KEYS = new Set([
28690
+ "url",
28691
+ "backgroundRemovedUrl",
28692
+ "overlayUrl",
28693
+ "posterUrl",
28694
+ "mp4Url",
28695
+ "webmUrl"
28696
+ ]);
28624
28697
  var IMAGE_OBJECT_JSON_SCHEMA = {
28625
28698
  type: "object",
28626
28699
  format: "image",
@@ -29985,7 +30058,7 @@ function defineCatalog(schema, catalog) {
29985
30058
  return schema.createCatalog(catalog);
29986
30059
  }
29987
30060
 
29988
- // ../../node_modules/.bun/@json-render+react@0.19.0+d86b59289c1a13ae/node_modules/@json-render/react/dist/chunk-WYDS23XB.mjs
30061
+ // ../../node_modules/.bun/@json-render+react@0.19.0+a3a949af4499b2f6/node_modules/@json-render/react/dist/chunk-WYDS23XB.mjs
29989
30062
  var schema = defineSchema((s) => ({
29990
30063
  spec: s.object({
29991
30064
  root: s.string(),
@@ -30040,7 +30113,7 @@ var schema = defineSchema((s) => ({
30040
30113
  ]
30041
30114
  });
30042
30115
 
30043
- // ../../node_modules/.bun/@json-render+shadcn@0.19.0+53a8df2c6ce486a8/node_modules/@json-render/shadcn/dist/chunk-L37EOULD.mjs
30116
+ // ../../node_modules/.bun/@json-render+shadcn@0.19.0+c4b94aca9866a541/node_modules/@json-render/shadcn/dist/chunk-L37EOULD.mjs
30044
30117
  var validationCheckSchema = array(object({
30045
30118
  type: string2(),
30046
30119
  message: string2(),
@@ -30415,6 +30488,372 @@ var shadcnComponentDefinitions = {
30415
30488
  }
30416
30489
  };
30417
30490
 
30491
+ // ../shared/src/json-render/card-catalog.ts
30492
+ var SCRIPT_CARD_TONES = [
30493
+ "neutral",
30494
+ "success",
30495
+ "failure",
30496
+ "critical",
30497
+ "fumble",
30498
+ "partial",
30499
+ "info",
30500
+ "magic"
30501
+ ];
30502
+ var SCRIPT_CARD_ICONS = [
30503
+ "sword",
30504
+ "swords",
30505
+ "shield",
30506
+ "heart",
30507
+ "skull",
30508
+ "coins",
30509
+ "scroll",
30510
+ "sparkles",
30511
+ "flame",
30512
+ "snowflake",
30513
+ "droplet",
30514
+ "dice",
30515
+ "key",
30516
+ "lock",
30517
+ "eye",
30518
+ "moon",
30519
+ "sun",
30520
+ "tent",
30521
+ "crown",
30522
+ "gem",
30523
+ "flask",
30524
+ "book",
30525
+ "target",
30526
+ "zap",
30527
+ "trophy",
30528
+ "users",
30529
+ "user",
30530
+ "map",
30531
+ "compass",
30532
+ "wand",
30533
+ "star",
30534
+ "check",
30535
+ "x",
30536
+ "hourglass",
30537
+ "footprints",
30538
+ "hammer",
30539
+ "cpu",
30540
+ "brain",
30541
+ "vote",
30542
+ "bag",
30543
+ "feather",
30544
+ "ghost",
30545
+ "leaf",
30546
+ "anchor",
30547
+ "castle",
30548
+ "music",
30549
+ "message",
30550
+ "alert",
30551
+ "info",
30552
+ "plus",
30553
+ "minus",
30554
+ "arrow"
30555
+ ];
30556
+ var DEFAULT_SLOT = ["default"];
30557
+ var tone = optional(nullable(_enum(SCRIPT_CARD_TONES)));
30558
+ var ICON_NAME_RE = /^[a-z][a-z-]*$/;
30559
+ var KNOWN_ICONS = new Set(SCRIPT_CARD_ICONS);
30560
+ var icon = optional(nullable(string2().max(16).refine((name) => !ICON_NAME_RE.test(name) || KNOWN_ICONS.has(name), {
30561
+ message: `not a card icon; use one of ${SCRIPT_CARD_ICONS.join(", ")}, or an emoji`
30562
+ })));
30563
+ var SCRIPT_CARD_COLORS = [
30564
+ "red",
30565
+ "orange",
30566
+ "gold",
30567
+ "green",
30568
+ "teal",
30569
+ "blue",
30570
+ "violet",
30571
+ "pink"
30572
+ ];
30573
+ var text = optional(nullable(string2()));
30574
+ var num = optional(nullable(number2()));
30575
+ var className = optional(nullable(string2()));
30576
+ var value = optional(nullable(union([string2(), number2()])));
30577
+ var TONE_DOC = `tone is one of ${SCRIPT_CARD_TONES.map((t) => `"${t}"`).join(", ")}: success emerald, failure rose, critical gold, fumble red, partial amber, info sky, magic violet.`;
30578
+ var ICON_DOC = `icon names a game icon (${SCRIPT_CARD_ICONS.join(", ")}) or is a short text such as an emoji.`;
30579
+ var DIE_DOC = `Each die is { sides, value, kept, tone, color, label, exploded }: sides 4, 6, 8, 10, 12, 20 or 100 draws that die's shape (any other is a square); value is its face, a number or a short text ("+", "−" and " " for Fate dice, "?" for a die players don't see); kept: false strikes it out (the low die of advantage, a pool's dropped dice); a kept d20's natural 20 glows gold and its natural 1 breaks; tone gives it a meaning (a Blades pool's highest die "partial"), while color only colors it (${SCRIPT_CARD_COLORS.join(", ")}: "orange" for fire); label is a word under it ("fire", "hope"); exploded: true marks a die that exploded.`;
30580
+ var scriptCardComponents = {
30581
+ Frame: {
30582
+ props: object({
30583
+ eyebrow: text,
30584
+ title: text,
30585
+ subtitle: text,
30586
+ icon,
30587
+ image: text,
30588
+ tag: text,
30589
+ tone,
30590
+ className
30591
+ }),
30592
+ slots: DEFAULT_SLOT,
30593
+ description: `Only in a script's card: the card's shell. A header (an icon or a portrait image, a small eyebrow line above the title such as "Attack" or "Shop", the title, a subtitle) and its children stacked below. The frame stays neutral and tone tints its icon; only "critical" (a gold rule and a foil sweep) and "fumble" (a red rule) mark the frame itself, so save them for a natural 20 or 1. image is a portrait URL in place of the icon (a merchant, a speaker). tag is a small label at the header's end ("Live", "Secret"). Keep eyebrow to two short parts ("Longsword · advantage"); put mechanics such as a roll's position in Tags. Put an Actions element last among its children for a footer of Buttons. ${TONE_DOC} ${ICON_DOC}`,
30594
+ example: {
30595
+ eyebrow: "Attack",
30596
+ title: "Brom swings at the goblin",
30597
+ subtitle: "Longsword · advantage",
30598
+ icon: "sword",
30599
+ tone: "success"
30600
+ }
30601
+ },
30602
+ Actions: {
30603
+ props: object({
30604
+ align: optional(nullable(_enum(["start", "end", "stretch"]))),
30605
+ className
30606
+ }),
30607
+ slots: DEFAULT_SLOT,
30608
+ description: `Only in a script's card: a row of Buttons. Last inside a Frame it is the frame's footer, set apart by a rule; elsewhere it is a plain row. align "stretch" gives each Button an equal share.`,
30609
+ example: { align: "start" }
30610
+ },
30611
+ Dice: {
30612
+ props: object({
30613
+ dice: array(unknown()),
30614
+ size: optional(nullable(_enum(["sm", "md", "lg"]))),
30615
+ className
30616
+ }),
30617
+ description: `Only in a script's card: dice as they landed, each drawn as its own shape with its face. ${DIE_DOC}`,
30618
+ example: {
30619
+ dice: [
30620
+ { sides: 20, value: 17 },
30621
+ { sides: 20, value: 6, kept: false }
30622
+ ],
30623
+ size: "md"
30624
+ }
30625
+ },
30626
+ Roll: {
30627
+ props: object({
30628
+ label: text,
30629
+ dice: array(unknown()),
30630
+ modifiers: optional(nullable(array(unknown()))),
30631
+ total: value,
30632
+ target: optional(nullable(object({ label: text, value }).passthrough())),
30633
+ outcome: optional(nullable(object({ text, tone }).passthrough())),
30634
+ className
30635
+ }),
30636
+ description: `Only in a script's card: one roll read as a whole: its dice, what was added, the total large, what it was against and the verdict as a stamp. dice as in Dice (${DIE_DOC}). modifiers is [{ value, label }] ({ value: 5, label: "STR" }). target is { label, value } ("AC", 15; "DC", 13). outcome is { text, tone } ("Hit" success, "Critical" critical, "Miss" failure, "Mixed" partial). The total is written by the script; the card doesn't add it up. A lone die with nothing added is its own total: it shows large with the verdict beside it. With a numeric target the card shows how far the roll beat or missed it, except on a natural 20 or 1.`,
30637
+ example: {
30638
+ label: "Attack roll",
30639
+ dice: [{ sides: 20, value: 17 }],
30640
+ modifiers: [{ value: 5, label: "STR" }],
30641
+ total: 22,
30642
+ target: { label: "AC", value: 15 },
30643
+ outcome: { text: "Hit", tone: "success" }
30644
+ }
30645
+ },
30646
+ Hero: {
30647
+ props: object({
30648
+ value: union([string2(), number2()]),
30649
+ label: text,
30650
+ unit: text,
30651
+ caption: text,
30652
+ icon,
30653
+ tone,
30654
+ className
30655
+ }),
30656
+ description: `Only in a script's card: the moment's one big number without dice: a level reached (label "Level", value 4), experience earned ("+450", unit "XP"), gold found, damage dealt, days left. tone "critical" celebrates it in gold with a burst (a level up, a milestone); other tones tint it. caption is a small line under the number. ${ICON_DOC}`,
30657
+ example: {
30658
+ label: "Level",
30659
+ value: 4,
30660
+ icon: "star",
30661
+ tone: "critical",
30662
+ caption: "Fighter · 2,700 XP"
30663
+ }
30664
+ },
30665
+ Outcome: {
30666
+ props: object({
30667
+ title: string2(),
30668
+ text,
30669
+ icon,
30670
+ tone,
30671
+ className
30672
+ }),
30673
+ description: `Only in a script's card: what happened, in words, as a band across the card: "The goblin falls", "Poisoned for 1 hour". title is the headline (keep it under about 60 characters: it shows two lines at most), text the detail under it. ${TONE_DOC}`,
30674
+ example: {
30675
+ title: "Poisoned for 1 hour",
30676
+ text: "Disadvantage on attack rolls and ability checks.",
30677
+ icon: "flask",
30678
+ tone: "failure"
30679
+ }
30680
+ },
30681
+ Meter: {
30682
+ props: object({
30683
+ label: text,
30684
+ value: number2(),
30685
+ max: number2(),
30686
+ min: num,
30687
+ from: num,
30688
+ icon,
30689
+ tone: optional(nullable(_enum([...SCRIPT_CARD_TONES, "health"]))),
30690
+ display: optional(nullable(_enum(["bar", "boxes", "pips"]))),
30691
+ better: optional(nullable(_enum(["higher", "lower"]))),
30692
+ caption: text,
30693
+ valueLabel: text,
30694
+ minLabel: text,
30695
+ maxLabel: text,
30696
+ hideValue: optional(nullable(boolean2())),
30697
+ className
30698
+ }),
30699
+ description: `Only in a script's card: a value against its max: hit points, mana, experience, a trace, votes in. from is where it was before this run: a loss drains and leaves an outlined ghost of what was lost (health shows a skull at zero), a gain glows, and the change shows signed beside the value. tone "health" (the default) goes green, amber, red as it empties; any other tone fixes the color. display "boxes" or "pips" draws one cell per point (supplies, death saves; up to 20); pips with an icon draw that icon in each (hearts, skulls). min below zero makes it two-sided around a centre tick (faction standing −5 to +5, sanity); valueLabel names where it stands ("Friendly"); minLabel and maxLabel label the ends (put them on the last of a group). better: "lower" when going up is bad (a trace, an alarm). caption is a small line under it. hideValue hides the numbers (an enemy's health). ${TONE_DOC}`,
30700
+ example: {
30701
+ label: "Hit points",
30702
+ value: 9,
30703
+ from: 21,
30704
+ max: 32,
30705
+ icon: "heart"
30706
+ }
30707
+ },
30708
+ Changes: {
30709
+ props: object({
30710
+ items: array(unknown()),
30711
+ className
30712
+ }),
30713
+ description: `Only in a script's card: what this run changed, a row each: { label, from, to, delta, unit, icon, detail, tone, better }. from → to shows before and after; delta is the signed difference (worked out from numbers when left out); a row with only delta reads "Gold −25 gp"; a row with only to reads as something gained ("New feature: Second Wind"). A row that didn't change is left out. Going up is green unless better is "lower"; tone "success" or "failure" overrides it. ${ICON_DOC}`,
30714
+ example: {
30715
+ items: [
30716
+ { label: "Hit point maximum", from: 32, to: 41, icon: "heart" },
30717
+ { label: "Gold", delta: -25, unit: "gp", icon: "coins" }
30718
+ ]
30719
+ }
30720
+ },
30721
+ Facts: {
30722
+ props: object({
30723
+ items: array(unknown()),
30724
+ layout: optional(nullable(_enum(["inline", "grid"]))),
30725
+ columns: num,
30726
+ className
30727
+ }),
30728
+ description: `Only in a script's card: short label/value pairs, [{ label, value, tone }]: DC 15 · Range 60 ft · Save WIS. layout "inline" (the default) runs them on one wrapping line; "grid" sets them in cells with the value large (columns, 2 to 4).`,
30729
+ example: {
30730
+ items: [
30731
+ { label: "DC", value: 15 },
30732
+ { label: "Range", value: "60 ft" }
30733
+ ],
30734
+ layout: "inline"
30735
+ }
30736
+ },
30737
+ Tags: {
30738
+ props: object({
30739
+ items: array(unknown()),
30740
+ className
30741
+ }),
30742
+ description: `Only in a script's card: small pills for conditions, damage types, traits: [{ text, tone, icon }] or plain strings. ${TONE_DOC}`,
30743
+ example: {
30744
+ items: [{ text: "Poisoned", tone: "fumble", icon: "flask" }, "Prone"]
30745
+ }
30746
+ },
30747
+ Roster: {
30748
+ props: object({
30749
+ items: array(unknown()),
30750
+ numbered: optional(nullable(boolean2())),
30751
+ tone,
30752
+ className
30753
+ }),
30754
+ description: `Only in a script's card: people in order: initiative, a party at rest, who voted. Each is { name, subtitle, image, value, valueLabel, dice, modifier, meter: { value, max }, tags, tone, state, you }: image is a portrait URL; without one the game shows the person's own portrait when it knows them by name (the player, a character, an extra), else initials; value is a number at the row's end with valueLabel under it ("19", "init"); dice ([{ sides, value, kept }], up to 3, as Dice takes them) show small before the value with modifier ("+2") between, for a roster of rolls such as initiative; meter is a thin health bar; state "current" lights the row (whose turn it is), "done" and "down" dim it ("down" strikes the name and shows a skull); you: true marks the player's own row. numbered shows 1, 2, 3. tone is the current row's accent (violet by default); an entry's own tone tints its initials ("failure" for enemies).`,
30755
+ example: {
30756
+ numbered: true,
30757
+ items: [
30758
+ {
30759
+ name: "Sera",
30760
+ subtitle: "Ranger",
30761
+ dice: [{ sides: 20, value: 16 }],
30762
+ modifier: "+3",
30763
+ value: 19,
30764
+ valueLabel: "init"
30765
+ },
30766
+ { name: "Goblin", value: 8, tone: "failure", state: "down" }
30767
+ ]
30768
+ }
30769
+ },
30770
+ RollTable: {
30771
+ props: object({
30772
+ rows: array(unknown()),
30773
+ landed: num,
30774
+ rolled: value,
30775
+ sides: num,
30776
+ className
30777
+ }),
30778
+ description: `Only in a script's card: a table of ranges with the row the roll landed on lit: a random encounter table, a move's bands (10+ / 7–9 / 6−), a percentile ladder. rows is [{ range, label, text, tone }]; landed is the index of the row that came up (0 first); rolled is the number shown on it; with sides (20) it shows as that die. text shows only on the landed row, so keep each label short and put what it means in text. ${TONE_DOC}`,
30779
+ example: {
30780
+ rows: [
30781
+ { range: "10+", label: "You do it", tone: "success" },
30782
+ { range: "7–9", label: "You stumble", tone: "partial" },
30783
+ { range: "6−", label: "The GM makes a move", tone: "failure" }
30784
+ ],
30785
+ landed: 1,
30786
+ rolled: 8
30787
+ }
30788
+ },
30789
+ Steps: {
30790
+ props: object({
30791
+ items: array(unknown()),
30792
+ direction: optional(nullable(_enum(["vertical", "horizontal"]))),
30793
+ tone,
30794
+ className
30795
+ }),
30796
+ description: `Only in a script's card: stages in order: a level up's choices, a ritual, clues found, a journey's legs. items is [{ label, detail, status }] with status "done", "current", "failed" or "todo" (the default). direction "horizontal" fits up to five short labels on one line. tone is the current stage's accent (violet by default).`,
30797
+ example: {
30798
+ items: [
30799
+ { label: "Roll hit points", status: "done" },
30800
+ { label: "Choose a feat", status: "current" },
30801
+ { label: "Review your sheet" }
30802
+ ]
30803
+ }
30804
+ },
30805
+ Log: {
30806
+ props: object({
30807
+ items: array(unknown()),
30808
+ className
30809
+ }),
30810
+ description: `Only in a script's card: what happened, line by line: a contest's rounds, a fight's blows, tries at a lock. items is [{ label, text, detail, tone }] or plain strings; label is a short marker ("R1", "Try 2"); detail is a small breakdown after the line ("16+5 · 9+4"); tone success or critical marks the line with a check, failure or fumble with a cross. ${TONE_DOC}`,
30811
+ example: {
30812
+ items: [
30813
+ { label: "R1", text: "21 vs 13", tone: "success" },
30814
+ { label: "R2", text: "9 vs 18", tone: "failure" }
30815
+ ]
30816
+ }
30817
+ },
30818
+ Currency: {
30819
+ props: object({
30820
+ amounts: array(unknown()),
30821
+ label: text,
30822
+ signed: optional(nullable(boolean2())),
30823
+ className
30824
+ }),
30825
+ description: `Only in a script's card: money as coins: [{ amount, unit, color }]. Units pp, gp, ep, sp and cp draw as platinum, gold, electrum, silver and copper coins; any other unit ("credits", "crowns") draws a plain coin, or one of color. signed: true shows amounts as changes, +14 green and −3 red. label is a small word before them ("Your purse").`,
30826
+ example: {
30827
+ label: "Your purse",
30828
+ amounts: [
30829
+ { amount: 23, unit: "gp" },
30830
+ { amount: 7, unit: "sp" }
30831
+ ]
30832
+ }
30833
+ },
30834
+ Versus: {
30835
+ props: object({
30836
+ left: object({}).passthrough(),
30837
+ right: object({}).passthrough(),
30838
+ winner: optional(nullable(_enum(["left", "right"]))),
30839
+ label: text,
30840
+ className
30841
+ }),
30842
+ description: `Only in a script's card: two sides head to head: a contest, an opposed check, a duel. left and right are { name, image, detail, value }: value is the score or roll shown large, detail a line under the name ("+5"). winner "left" or "right" crowns that side and dims the other; leave it out while it's undecided. label is a small line under "VS" ("best of 3").`,
30843
+ example: {
30844
+ left: { name: "Brom", detail: "+5", value: 2 },
30845
+ right: { name: "Old Hask", detail: "+4", value: 1 },
30846
+ winner: "left",
30847
+ label: "best of 3"
30848
+ }
30849
+ }
30850
+ };
30851
+ var SCRIPT_CARD_ONLY_COMPONENTS = new Set([
30852
+ "FileTiles",
30853
+ "Button",
30854
+ ...Object.keys(scriptCardComponents)
30855
+ ]);
30856
+
30418
30857
  // ../shared/src/json-render/catalog.ts
30419
30858
  var {
30420
30859
  Separator,
@@ -30474,7 +30913,7 @@ var extendedShadcnComponents = {
30474
30913
  Stack: {
30475
30914
  props: object({
30476
30915
  direction: nullable(_enum(["horizontal", "vertical"])),
30477
- gap: nullable(_enum(["sm", "md", "lg", "none"])),
30916
+ gap: nullable(_enum(["xs", "sm", "md", "lg", "none"])),
30478
30917
  align: nullable(_enum(["start", "center", "end", "stretch"])),
30479
30918
  justify: nullable(_enum(["start", "center", "end", "between", "around"])),
30480
30919
  wrap: nullable(boolean2()),
@@ -30491,7 +30930,7 @@ var extendedShadcnComponents = {
30491
30930
  Grid: {
30492
30931
  props: object({
30493
30932
  columns: nullable(number2()),
30494
- gap: nullable(_enum(["sm", "md", "lg"])),
30933
+ gap: nullable(_enum(["xs", "sm", "md", "lg"])),
30495
30934
  bgColor: nullable(string2()),
30496
30935
  bgImage: bgImageProp,
30497
30936
  bgImageFit: bgImageFitEnum,
@@ -30704,6 +31143,48 @@ var craftComponents = {
30704
31143
  bgColor: null
30705
31144
  }
30706
31145
  },
31146
+ FileTiles: {
31147
+ props: object({
31148
+ files: array(unknown()),
31149
+ size: nullable(_enum(["sm", "md", "lg"])),
31150
+ layout: nullable(_enum(["grid", "list", "row"])),
31151
+ emptyText: nullable(string2()),
31152
+ shown: nullable(number2().int().min(1)),
31153
+ moreLabel: nullable(string2()),
31154
+ moreLayout: nullable(_enum(["grid", "list", "row"])),
31155
+ className: classNameProp
31156
+ }),
31157
+ description: `Only in a script's card: a grid of the world's files, each its picture and name, opening the file on a tap in play. files lists them by id (a handle's $id, or the handle itself), or as { file, detail, badge, button } for a tile with a line under its name, a badge on its picture and a button that runs one of the script's actions ({ label, press: { action, params, confirm }, disabled, reason }). Craft fills in each name and picture after the run and leaves out any that aren't in the game. layout is how the tiles sit: "grid" (the default), "list" (a row per file, its button at the end: for comparing many, like prices) or "row" (one line that scrolls sideways, keeping the story in view). size is the tile size, "md" by default; a grid's columns follow the size and the screen. shown keeps the set to that many tiles, with a button (moreLabel, "See all" by default) that opens every tile in a window players can scroll and search, laid out as moreLayout (the card's layout by default, a grid for a row).`,
31158
+ example: {
31159
+ files: [],
31160
+ size: "md",
31161
+ layout: "grid",
31162
+ emptyText: null,
31163
+ shown: null,
31164
+ moreLabel: null,
31165
+ moreLayout: null
31166
+ }
31167
+ },
31168
+ Button: {
31169
+ props: object({
31170
+ label: string2(),
31171
+ variant: nullable(_enum(["default", "secondary", "outline", "danger", "choice"])),
31172
+ disabled: nullable(boolean2()),
31173
+ icon: optional(nullable(string2().max(16))),
31174
+ description: optional(nullable(string2())),
31175
+ meta: optional(nullable(union([string2(), number2()]))),
31176
+ selected: optional(nullable(boolean2())),
31177
+ reason: optional(nullable(string2())),
31178
+ turn: optional(nullable(string2().max(200))),
31179
+ className: classNameProp
31180
+ }),
31181
+ description: `Only in a script's card: a button that runs what its element's on.press names. One of the script's actions ({ action: "buy", params: { item: "silver-ring" } }) changes the game's files and redraws the card; setState, pushState and removeState change the card's own state. disabled (often a $cond) greys it out, and reason says why ("Need 27 gp"). variant "default" is the card's one primary action, "secondary" the rest, "danger" for something that can't be undone. variant "choice" draws it as an option row, full width: icon, label, description under it, meta at its end (a price, a tally: "5 gp", "2 votes"). Set selected (often a $cond on the card's state) when the choices are a pick among them: a check shows on the picked one. Once the pick is made and the card redraws, keep selected and set disabled on every choice: the chosen row stays bright with a check and the rest fade. icon names a card icon (sword, shield, heart, coins, flask…) or is an emoji. meta also shows after a plain Button's label. When its action starts the next turn (startsTurn on the action), the press is sent as the player's turn in the Button's label, or in turn when set: words that read as what the player does, for a Confirm whose label says nothing of the pick (often a $template on the card's state).`,
31182
+ example: {
31183
+ label: "Buy · 15 gp",
31184
+ variant: "secondary",
31185
+ disabled: null
31186
+ }
31187
+ },
30707
31188
  ReferenceValue: {
30708
31189
  props: object({
30709
31190
  source: unknown(),
@@ -31256,7 +31737,8 @@ var craftCatalog = defineCatalog(schema, {
31256
31737
  Avatar: withClassNameProp(Avatar),
31257
31738
  Alert: withClassNameProp(Alert),
31258
31739
  ...extendedShadcnComponents,
31259
- ...craftComponents
31740
+ ...craftComponents,
31741
+ ...scriptCardComponents
31260
31742
  },
31261
31743
  actions: {}
31262
31744
  });
@@ -31682,6 +32164,14 @@ function getCatalogPropSchemas(componentType) {
31682
32164
  }
31683
32165
  return props.shape;
31684
32166
  }
32167
+ function catalogPropValueProblem(componentType, propKey, value) {
32168
+ const fieldSchema = getCatalogPropSchemas(componentType)?.[propKey];
32169
+ if (!isPropFieldSchema(fieldSchema)) {
32170
+ return null;
32171
+ }
32172
+ const parsed = fieldSchema.safeParse(value);
32173
+ return parsed.success ? null : parsed.error?.issues?.[0]?.message ?? "does not match the schema";
32174
+ }
31685
32175
  function isCatalogComponentType(componentType) {
31686
32176
  return Object.hasOwn(craftCatalog.data.components, componentType);
31687
32177
  }
@@ -31822,6 +32312,9 @@ function collectJsonRenderLayoutCatalogIssues(input) {
31822
32312
  for (const [key, element] of Object.entries(parsed.elements)) {
31823
32313
  if (isRecord10(element)) {
31824
32314
  collectElementCatalogIssues(key, element, issues);
32315
+ if (typeof element.type === "string" && SCRIPT_CARD_ONLY_COMPONENTS.has(element.type)) {
32316
+ issues.push(`Element "${key}" is a ${element.type}, which only a script's card can use.`);
32317
+ }
31825
32318
  }
31826
32319
  }
31827
32320
  for (const [sectionId, section] of Object.entries(layoutSectionsFromSpec(parsed))) {
@@ -31980,10 +32473,14 @@ function findScriptFileType(types, key) {
31980
32473
  var SCRIPT_SOURCE_MAX_LENGTH = 64 * 1024;
31981
32474
  var SCRIPT_DESCRIPTION_MAX_LENGTH = 1000;
31982
32475
  var SCRIPT_WHEN_MAX_LENGTH = 500;
32476
+ var SCRIPT_AFTER_MAX_LENGTH = 500;
31983
32477
  var SCRIPT_MEMORY_LIMIT_BYTES = 16 * 1024 * 1024;
31984
32478
  var SCRIPT_RESULT_MAX_BYTES = 64 * 1024;
32479
+ var SCRIPT_CARD_MAX_BYTES = 16 * 1024;
32480
+ var SCRIPT_MAX_ACTIONS = 20;
32481
+ var SCRIPT_CARD_STATE_MAX_BYTES = 4 * 1024;
31985
32482
  var SCRIPT_LOG_MAX_BYTES = 16 * 1024;
31986
- var SCRIPT_MAX_PER_CONTAINER = 500;
32483
+ var SCRIPT_MAX_PER_CONTAINER = 30;
31987
32484
 
31988
32485
  // ../shared/src/scripts/name.ts
31989
32486
  var SCRIPT_FILE_SUFFIX_RE = /\.script(\.js)?$/i;
@@ -32003,6 +32500,44 @@ function scriptFunctionName(fileName) {
32003
32500
  return LEADING_DIGIT_RE.test(name) ? `_${name}` : name;
32004
32501
  }
32005
32502
 
32503
+ // ../shared/src/scripts/card.ts
32504
+ var SCRIPT_CARD_EXCLUDED_COMPONENTS = new Set([
32505
+ "Include",
32506
+ "SectionSwitch",
32507
+ "FileReferenceGrid",
32508
+ "PageReferenceGrid",
32509
+ "ReferenceValue",
32510
+ "TurnTracker",
32511
+ "Select",
32512
+ "VoiceSelect"
32513
+ ]);
32514
+ var SCRIPT_CARD_STATE_ACTIONS = new Set([
32515
+ "setState",
32516
+ "pushState",
32517
+ "removeState"
32518
+ ]);
32519
+ var SCRIPT_CARD_RESERVED_ACTIONS = new Set([
32520
+ ...SCRIPT_CARD_STATE_ACTIONS,
32521
+ "validateForm"
32522
+ ]);
32523
+ var BINDING_KEYS = new Set([
32524
+ "action",
32525
+ "params",
32526
+ "confirm",
32527
+ "onSuccess",
32528
+ "onError",
32529
+ "preventDefault"
32530
+ ]);
32531
+ var TILE_KEYS = new Set(["file", "detail", "badge", "button"]);
32532
+ var TILE_BUTTON_KEYS = new Set([
32533
+ "label",
32534
+ "press",
32535
+ "disabled",
32536
+ "reason",
32537
+ "variant"
32538
+ ]);
32539
+ var TILE_BUTTON_VARIANTS = new Set(["default", "secondary", "outline"]);
32540
+
32006
32541
  // ../shared/src/scripts/types.ts
32007
32542
  var SCRIPT_FIELD_TYPES = [
32008
32543
  "string",
@@ -32013,6 +32548,7 @@ var SCRIPT_FIELD_TYPES = [
32013
32548
  "object"
32014
32549
  ];
32015
32550
  var SCRIPT_VISIBILITIES = ["gm", "player"];
32551
+ var SCRIPT_CARD_ORDERS = ["together", "card-first"];
32016
32552
 
32017
32553
  // ../shared/src/scripts/fields.ts
32018
32554
  var FIELD_NAME_RE = /^[A-Za-z_$][\w$]*$/;
@@ -32243,10 +32779,16 @@ var BANNED_MEMBER_NAMES2 = new Set(["__proto__", "constructor", "prototype"]);
32243
32779
  var DEFINITION_KEYS = new Set([
32244
32780
  "description",
32245
32781
  "when",
32782
+ "after",
32246
32783
  "input",
32247
32784
  "run",
32248
- "visibility"
32785
+ "visibility",
32786
+ "cardOrder",
32787
+ "waitsForPlayer",
32788
+ "card",
32789
+ "actions"
32249
32790
  ]);
32791
+ var ACTION_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
32250
32792
  var ACORN_POSITION_SUFFIX_RE = /\s*\(\d+:\d+\)$/;
32251
32793
 
32252
32794
  class LiteralError extends Error {
@@ -32335,6 +32877,23 @@ function stringOption(property, key, maxLength, diagnostics) {
32335
32877
  throw error;
32336
32878
  }
32337
32879
  }
32880
+ function cardOrderOption(property, diagnostics) {
32881
+ const value = property.value.type === "Literal" ? property.value.value : undefined;
32882
+ const order = SCRIPT_CARD_ORDERS.find((option) => option === value);
32883
+ if (!order) {
32884
+ diagnostics.push(at(property.value, 'cardOrder is "together" (the card shows with what the run did on the map, the default) or "card-first" (the card shows first, then the map plays)'));
32885
+ return "together";
32886
+ }
32887
+ return order;
32888
+ }
32889
+ function booleanOption(property, key, diagnostics) {
32890
+ const value = property.value.type === "Literal" ? property.value.value : undefined;
32891
+ if (typeof value !== "boolean") {
32892
+ diagnostics.push(at(property.value, `${key} is true or false`));
32893
+ return false;
32894
+ }
32895
+ return value;
32896
+ }
32338
32897
  function visibilityOption(property, diagnostics) {
32339
32898
  const value = property.value.type === "Literal" ? property.value.value : undefined;
32340
32899
  const visibility = SCRIPT_VISIBILITIES.find((option) => option === value);
@@ -32358,14 +32917,193 @@ function checkRun(property, diagnostics) {
32358
32917
  }
32359
32918
  return { ok: true, usesGame: value.params.length >= 2 };
32360
32919
  }
32920
+ function checkCard(property, diagnostics) {
32921
+ const value = property.value;
32922
+ if (value.type !== "FunctionExpression" && value.type !== "ArrowFunctionExpression") {
32923
+ diagnostics.push(at(value, "card must be a function: card(result, input) { return { root, elements } }"));
32924
+ return false;
32925
+ }
32926
+ if (value.generator) {
32927
+ diagnostics.push(at(property, "card can't be a generator"));
32928
+ }
32929
+ if (value.params.length > 2) {
32930
+ diagnostics.push(at(value, "card takes the result and the input: card(result, input)"));
32931
+ }
32932
+ checkCardLiterals(value, diagnostics);
32933
+ return true;
32934
+ }
32935
+ function actionFields(node, name, diagnostics) {
32936
+ let value = {};
32937
+ try {
32938
+ value = literalValue(node);
32939
+ } catch (error) {
32940
+ if (!(error instanceof LiteralError)) {
32941
+ throw error;
32942
+ }
32943
+ diagnostics.push(at(error.node, `actions.${name}.input: ${error.message}`));
32944
+ }
32945
+ const { fields, errors } = readScriptFields(value);
32946
+ for (const error of errors) {
32947
+ diagnostics.push(at(node, `actions.${name}.input: ${error}`));
32948
+ }
32949
+ return { fields, inputSchema: fieldsToJsonSchema(fields) };
32950
+ }
32951
+ function checkActions(property, diagnostics) {
32952
+ const actions = {};
32953
+ if (property.value.type !== "ObjectExpression") {
32954
+ diagnostics.push(at(property.value, "actions must be an object: actions: { buy: { input: { ... }, run(input, { game, result }) { ... } } }"));
32955
+ return actions;
32956
+ }
32957
+ for (const entry of property.value.properties) {
32958
+ if (entry.type !== "Property" || entry.computed) {
32959
+ diagnostics.push(at(entry, "Write each action out by name; no ...spread"));
32960
+ continue;
32961
+ }
32962
+ const name = propertyKey(entry);
32963
+ if (name === null || !ACTION_NAME_RE.test(name)) {
32964
+ diagnostics.push(at(entry.key, "An action's name is a plain name, like buy or takeFeat"));
32965
+ continue;
32966
+ }
32967
+ if (SCRIPT_CARD_RESERVED_ACTIONS.has(name)) {
32968
+ diagnostics.push(at(entry.key, `${name} is one of the card's own actions; give this action another name`));
32969
+ continue;
32970
+ }
32971
+ if (name in actions) {
32972
+ diagnostics.push(at(entry.key, `There are two actions named ${name}`));
32973
+ continue;
32974
+ }
32975
+ if (entry.value.type !== "ObjectExpression") {
32976
+ diagnostics.push(at(entry.value, `actions.${name} must be an object: { input: { ... }, run(input, { game, result }) { ... } }`));
32977
+ continue;
32978
+ }
32979
+ let definition = {
32980
+ fields: {},
32981
+ inputSchema: fieldsToJsonSchema({})
32982
+ };
32983
+ let hasRun = false;
32984
+ let startsTurn = false;
32985
+ for (const part of entry.value.properties) {
32986
+ const key = part.type === "Property" ? propertyKey(part) : null;
32987
+ if (part.type !== "Property" || key === "input") {
32988
+ if (part.type === "Property") {
32989
+ definition = actionFields(part.value, name, diagnostics);
32990
+ } else {
32991
+ diagnostics.push(at(part, "Write the action out; no ...spread"));
32992
+ }
32993
+ continue;
32994
+ }
32995
+ if (key === "startsTurn") {
32996
+ startsTurn = booleanOption(part, `actions.${name}.startsTurn`, diagnostics);
32997
+ continue;
32998
+ }
32999
+ if (key !== "run") {
33000
+ diagnostics.push(at(part.key, `actions.${name} takes input, run and startsTurn${key ? `, not "${key}"` : ""}`));
33001
+ continue;
33002
+ }
33003
+ const value = part.value;
33004
+ if (value.type !== "FunctionExpression" && value.type !== "ArrowFunctionExpression") {
33005
+ diagnostics.push(at(value, `actions.${name}.run must be a function: run(input, { game, result }) { ... }`));
33006
+ continue;
33007
+ }
33008
+ if (value.generator) {
33009
+ diagnostics.push(at(part, `actions.${name}.run can't be a generator`));
33010
+ }
33011
+ if (value.params.length > 2) {
33012
+ diagnostics.push(at(value, `actions.${name}.run takes the input and the context: run(input, { game, result })`));
33013
+ }
33014
+ hasRun = true;
33015
+ }
33016
+ if (!hasRun) {
33017
+ diagnostics.push(at(entry, `actions.${name} needs run(input, { game, result }) { ... }`));
33018
+ }
33019
+ actions[name] = startsTurn ? { ...definition, startsTurn } : definition;
33020
+ }
33021
+ if (Object.keys(actions).length > SCRIPT_MAX_ACTIONS) {
33022
+ diagnostics.push(at(property.value, `A script can have at most ${SCRIPT_MAX_ACTIONS} actions; this one has ${Object.keys(actions).length}`));
33023
+ }
33024
+ return actions;
33025
+ }
33026
+ function propertyNamed(node, name) {
33027
+ for (const property of node.properties) {
33028
+ if (property.type === "Property" && propertyKey(property) === name) {
33029
+ return property;
33030
+ }
33031
+ }
33032
+ return null;
33033
+ }
33034
+ function writtenValues(node) {
33035
+ if (node.type === "Literal") {
33036
+ const value = node.value;
33037
+ return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" ? [[value, node]] : [];
33038
+ }
33039
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
33040
+ return [[node.quasis[0]?.value.cooked ?? "", node]];
33041
+ }
33042
+ if (node.type === "ConditionalExpression") {
33043
+ return [
33044
+ ...writtenValues(node.consequent),
33045
+ ...writtenValues(node.alternate)
33046
+ ];
33047
+ }
33048
+ if (node.type === "LogicalExpression" && (node.operator === "||" || node.operator === "??")) {
33049
+ return writtenValues(node.right);
33050
+ }
33051
+ return [];
33052
+ }
33053
+ function checkCardLiterals(card, diagnostics) {
33054
+ walk2(card, (node) => {
33055
+ if (node.type !== "ObjectExpression") {
33056
+ return;
33057
+ }
33058
+ const typeProperty = propertyNamed(node, "type");
33059
+ const props = propertyNamed(node, "props");
33060
+ if (!(typeProperty && (props || propertyNamed(node, "children")))) {
33061
+ return;
33062
+ }
33063
+ const [written] = writtenValues(typeProperty.value);
33064
+ const componentType = written?.[0];
33065
+ if (typeof componentType !== "string") {
33066
+ return;
33067
+ }
33068
+ if (SCRIPT_CARD_EXCLUDED_COMPONENTS.has(componentType)) {
33069
+ diagnostics.push(at(typeProperty.value, `A card can't use ${componentType}`));
33070
+ return;
33071
+ }
33072
+ if (!isCatalogComponentType(componentType)) {
33073
+ diagnostics.push(at(typeProperty.value, `${componentType} isn't a component; see the layout component table`));
33074
+ return;
33075
+ }
33076
+ if (props?.value.type !== "ObjectExpression") {
33077
+ return;
33078
+ }
33079
+ for (const prop of props.value.properties) {
33080
+ const propKey = prop.type === "Property" ? propertyKey(prop) : null;
33081
+ if (!(prop.type === "Property" && propKey)) {
33082
+ continue;
33083
+ }
33084
+ for (const [value, valueNode] of writtenValues(prop.value)) {
33085
+ const problem = catalogPropValueProblem(componentType, propKey, value);
33086
+ if (problem) {
33087
+ diagnostics.push(at(valueNode, `${componentType} ${propKey} can't be ${JSON.stringify(value)}: ${problem}`));
33088
+ }
33089
+ }
33090
+ }
33091
+ });
33092
+ }
32361
33093
  function readDefinition(options, diagnostics) {
32362
33094
  let description = null;
32363
33095
  let when = null;
33096
+ let after = null;
32364
33097
  let visibility = "gm";
33098
+ let cardOrder = "together";
33099
+ let waitsForPlayer = false;
32365
33100
  let fieldsValue = {};
32366
33101
  let fieldsNode = options;
32367
33102
  let hasRun = false;
32368
33103
  let usesGame = false;
33104
+ let hasCard = false;
33105
+ let actions = {};
33106
+ let actionsNode = null;
32369
33107
  for (const property of options.properties) {
32370
33108
  if (property.type !== "Property") {
32371
33109
  diagnostics.push(at(property, "Write defineScript()'s options out; no ...spread"));
@@ -32377,15 +33115,26 @@ function readDefinition(options, diagnostics) {
32377
33115
  continue;
32378
33116
  }
32379
33117
  if (key === null || !DEFINITION_KEYS.has(key)) {
32380
- diagnostics.push(at(property.key, `defineScript() takes description, when, input, run and visibility${key ? `, not "${key}"` : ""}`));
33118
+ diagnostics.push(at(property.key, `defineScript() takes description, when, after, input, run, visibility, waitsForPlayer, cardOrder, card and actions${key ? `, not "${key}"` : ""}`));
32381
33119
  continue;
32382
33120
  }
32383
33121
  if (key === "description") {
32384
33122
  description = stringOption(property, key, SCRIPT_DESCRIPTION_MAX_LENGTH, diagnostics);
32385
33123
  } else if (key === "when") {
32386
33124
  when = stringOption(property, key, SCRIPT_WHEN_MAX_LENGTH, diagnostics) || null;
33125
+ } else if (key === "after") {
33126
+ after = stringOption(property, key, SCRIPT_AFTER_MAX_LENGTH, diagnostics) || null;
32387
33127
  } else if (key === "visibility") {
32388
33128
  visibility = visibilityOption(property, diagnostics);
33129
+ } else if (key === "cardOrder") {
33130
+ cardOrder = cardOrderOption(property, diagnostics);
33131
+ } else if (key === "waitsForPlayer") {
33132
+ waitsForPlayer = booleanOption(property, key, diagnostics);
33133
+ } else if (key === "card") {
33134
+ hasCard = checkCard(property, diagnostics);
33135
+ } else if (key === "actions") {
33136
+ actions = checkActions(property, diagnostics);
33137
+ actionsNode = property.key;
32389
33138
  } else if (key === "input") {
32390
33139
  fieldsNode = property.value;
32391
33140
  try {
@@ -32409,6 +33158,9 @@ function readDefinition(options, diagnostics) {
32409
33158
  if (!hasRun) {
32410
33159
  diagnostics.push(at(options, "defineScript() needs run(input) { ... } (or run(input, { game }) to read and change files)"));
32411
33160
  }
33161
+ if (actionsNode && !(hasCard && visibility === "player")) {
33162
+ diagnostics.push(at(actionsNode, 'actions run from Buttons on the card players see: give the script card(result, input) and visibility: "player"'));
33163
+ }
32412
33164
  const { fields, errors } = readScriptFields(fieldsValue);
32413
33165
  for (const error of errors) {
32414
33166
  diagnostics.push(at(fieldsNode, error));
@@ -32419,10 +33171,15 @@ function readDefinition(options, diagnostics) {
32419
33171
  return {
32420
33172
  description,
32421
33173
  when,
33174
+ after,
32422
33175
  visibility,
33176
+ cardOrder,
33177
+ waitsForPlayer,
32423
33178
  fields,
32424
33179
  inputSchema: fieldsToJsonSchema(fields),
32425
- usesGame
33180
+ usesGame,
33181
+ hasCard,
33182
+ actions
32426
33183
  };
32427
33184
  }
32428
33185
  function isAstNode2(value) {
@@ -32595,7 +33352,7 @@ function definitionCall(declaration, diagnostics) {
32595
33352
  }
32596
33353
  const [options, ...extra] = call.arguments;
32597
33354
  if (!options || options.type !== "ObjectExpression" || extra.length > 0) {
32598
- diagnostics.push(at(call, "defineScript() takes one object: { description, when, input, run, visibility }"));
33355
+ diagnostics.push(at(call, "defineScript() takes one object: { description, when, input, run, visibility, cardOrder, card, actions }"));
32599
33356
  return null;
32600
33357
  }
32601
33358
  return options;
@@ -33619,10 +34376,89 @@ var ARRIVAL_KEYS = new Set([
33619
34376
  "details",
33620
34377
  "hidden"
33621
34378
  ]);
33622
- var STAMPS = new Set(["since", "changed"]);
34379
+ var STAMPS = new Set(["since", "changed", "sinceTurn", "changedTurn"]);
33623
34380
  // ../shared/src/scripts/input.ts
33624
34381
  var import_ajv2 = __toESM(require_ajv());
33625
34382
  var compiled = new WeakMap;
34383
+ // ../shared/src/scene-looks.ts
34384
+ var ROLL_KINDS = [
34385
+ "attack",
34386
+ "damage",
34387
+ "heal",
34388
+ "save",
34389
+ "check",
34390
+ "contest",
34391
+ "chance"
34392
+ ];
34393
+ var DELIVERIES = [
34394
+ "strike",
34395
+ "claw",
34396
+ "shot",
34397
+ "thrown",
34398
+ "bolt",
34399
+ "beam",
34400
+ "arc",
34401
+ "wave",
34402
+ "spray",
34403
+ "burst",
34404
+ "touch"
34405
+ ];
34406
+ var PROJECTILES = [
34407
+ "shaft",
34408
+ "blade",
34409
+ "ball",
34410
+ "slug",
34411
+ "missile",
34412
+ "bead",
34413
+ "line",
34414
+ "swarm"
34415
+ ];
34416
+ var ELEMENTS = [
34417
+ "physical",
34418
+ "fire",
34419
+ "cold",
34420
+ "lightning",
34421
+ "acid",
34422
+ "poison",
34423
+ "shadow",
34424
+ "light",
34425
+ "mind",
34426
+ "arcane",
34427
+ "sonic",
34428
+ "nature",
34429
+ "life"
34430
+ ];
34431
+ var APPROACHES = [
34432
+ "sneak",
34433
+ "notice",
34434
+ "search",
34435
+ "read",
34436
+ "persuade",
34437
+ "deceive",
34438
+ "intimidate",
34439
+ "perform",
34440
+ "force",
34441
+ "agility",
34442
+ "endure",
34443
+ "know",
34444
+ "tinker",
34445
+ "tend",
34446
+ "wild",
34447
+ "magic"
34448
+ ];
34449
+ var REACTIONS = [
34450
+ "alert",
34451
+ "question",
34452
+ "love",
34453
+ "anger",
34454
+ "fear",
34455
+ "joy",
34456
+ "sleep",
34457
+ "idea",
34458
+ "sad",
34459
+ "silence"
34460
+ ];
34461
+ var BLOW_KINDS = new Set(["attack", "damage", "heal"]);
33626
34462
  // ../shared/src/scripts/script-types.ts
33627
34463
  var SCRIPT_TYPES_COMPILER_OPTIONS = {
33628
34464
  target: "ES2022",
@@ -33656,6 +34492,7 @@ var HANDLE_MEMBER_NAMES = new Set([
33656
34492
  "$path",
33657
34493
  "$fileTypeSlug"
33658
34494
  ]);
34495
+ var union2 = (list) => list.map((entry) => JSON.stringify(entry)).join(" | ");
33659
34496
  var PRELUDE = `/** A reference to another file, as a reference field stores it. */
33660
34497
  interface StoredReference {
33661
34498
  readonly slug: string;
@@ -33761,6 +34598,10 @@ interface CurrentStatePerson {
33761
34598
  readonly hidden: CurrentStateHidden | null;
33762
34599
  /** An extra's own state (hp, conditions); someone with a file keeps theirs in it. */
33763
34600
  readonly details: { readonly [detail: string]: string | number | boolean };
34601
+ /** The map square(s) they stand on ("M13", "J9–K10"); null off a grid or with no token. */
34602
+ readonly square: string | null;
34603
+ /** Feet to everyone else here with a token, by name, as the map measures it; null off a map. */
34604
+ readonly distances: { readonly [name: string]: number } | null;
33764
34605
  }
33765
34606
 
33766
34607
  /** A file or person a tracker points at, by name; its file is filled in by the game. */
@@ -33913,8 +34754,75 @@ interface CurrentStateChange {
33913
34754
  /** The current state: the scene the GM keeps. */
33914
34755
  interface GameCurrentState {
33915
34756
  get(): Promise<CurrentState>;
33916
- /** Checks the change (an error says what's wrong, with error.code) and returns the current state with it. */
33917
- update(change: CurrentStateChange): Promise<CurrentState>;
34757
+ /**
34758
+ * Checks the change (an error says what's wrong, with error.code) and
34759
+ * returns the current state with it. options.show is where what it makes
34760
+ * shows (an HP change's row, someone going down): see Show.
34761
+ */
34762
+ update(change: CurrentStateChange, options?: { show?: Show }): Promise<CurrentState>;
34763
+ }
34764
+
34765
+ /**
34766
+ * Where a roll, an effect, a reaction or a current-state change shows:
34767
+ * "all" (the default) on the map and as its row in the story; "map" on the
34768
+ * map only, when the script's own card tells it in the story; "none"
34769
+ * nowhere (it still happens: the dice decide, the state changes).
34770
+ */
34771
+ type Show = "all" | "map" | "none";
34772
+
34773
+ /** How a moment looks on the map (knowledge/map-language.md). */
34774
+ interface MomentLook {
34775
+ /** How it travels from who it comes from to who it lands on. */
34776
+ delivery?: ${union2(DELIVERIES)};
34777
+ /** What the eye sees flying, for a shot, a throw or a bolt: a javelin is "shaft", a pistol "slug". */
34778
+ projectile?: ${union2(PROJECTILES)};
34779
+ element?: ${union2(ELEMENTS)};
34780
+ }
34781
+
34782
+ /** What game.roll() takes besides the dice. */
34783
+ interface RollOptions extends MomentLook {
34784
+ /** Who rolls, by their current-state name; leave out for a roll no one in the scene makes. */
34785
+ by?: string;
34786
+ /** Who it is at or for: the target, or everyone an area catches. */
34787
+ at?: string | readonly string[];
34788
+ /** What the roll is for, a few words ("Scimitar attack"). */
34789
+ reason?: string;
34790
+ /** The number it is against: 14, or { value: 14, label: "AC 14" }. */
34791
+ against?: number | { value: number; label?: string; succeedsOn?: "or_higher" | "or_lower" };
34792
+ /** What kind of roll the map shows. */
34793
+ kind?: ${union2(ROLL_KINDS)};
34794
+ approach?: ${union2(APPROACHES)};
34795
+ /** Where the roll shows: "all" (the default), "map" (no row in the story) or "none". */
34796
+ show?: Show;
34797
+ }
34798
+
34799
+ /** What game.roll() returns: the total, every face, and the kept d20 face when there is one. */
34800
+ interface RollResult {
34801
+ total: number;
34802
+ faces: number[];
34803
+ /** The kept face of the first d20, for natural 1s and 20s. */
34804
+ natural?: number;
34805
+ dice: string;
34806
+ }
34807
+
34808
+ /** What game.effect() takes: something landing on people with no roll of its own. */
34809
+ interface EffectOptions extends MomentLook {
34810
+ /** Who it comes from; leave out when it comes from the place itself. */
34811
+ from?: string;
34812
+ /** Who it lands on. */
34813
+ at: string | readonly string[];
34814
+ /** What happened, a few words for the caption ("Scimitar hits"). */
34815
+ what: string;
34816
+ /** Where it shows: "all" (the default), "map" (no row in the story) or "none". */
34817
+ show?: Show;
34818
+ }
34819
+
34820
+ /** What game.react() takes: someone visibly reacting, a bead over their head. */
34821
+ interface ReactOptions {
34822
+ who: string;
34823
+ as: ${union2(REACTIONS)};
34824
+ /** Where it shows: "all" (the default), "map" or "none". */
34825
+ show?: Show;
33918
34826
  }
33919
34827
 
33920
34828
  /** run()'s second argument. */
@@ -33927,6 +34835,34 @@ interface ScriptContext {
33927
34835
  readonly game: Game;
33928
34836
  }
33929
34837
 
34838
+ /** An action's second argument: the game, and the card's last result. */
34839
+ interface ScriptActionContext {
34840
+ /** The world's files, as run() gets them; every change saves together or none does. */
34841
+ readonly game: Game;
34842
+ /** What the card was last drawn from: run()'s result, or the last action's. Read-only. */
34843
+ readonly result: any;
34844
+ }
34845
+
34846
+ /** Something a card's Button can run: \`on: { press: { action: "buy", params: { ... } } }\`. */
34847
+ interface ScriptAction {
34848
+ /** The fields a tap passes as params, by name. */
34849
+ input?: { [field: string]: ScriptInputField };
34850
+ /**
34851
+ * Runs on a tap. Check what the tap asks against the result and the files
34852
+ * (never trust params for a price), throw to refuse with a reason the card
34853
+ * shows, and return the card's next result (or nothing to keep it).
34854
+ */
34855
+ run(input: any, context: ScriptActionContext): unknown;
34856
+ /**
34857
+ * Start the Game Master's next turn once a press works (default false),
34858
+ * with the pressed Button's label as the player's turn: for a pick-one
34859
+ * choice that moves the story on. Only while the card is the newest thing
34860
+ * in the story, and only the first such press on it. Leave it off for an
34861
+ * action players press many times, like a shop's Buy.
34862
+ */
34863
+ startsTurn?: boolean;
34864
+ }
34865
+
33930
34866
  /** One input field: a small JSON Schema. Required unless optional or given a default. */
33931
34867
  interface ScriptInputField {
33932
34868
  type: "string" | "number" | "integer" | "boolean" | "array" | "object";
@@ -33950,12 +34886,71 @@ interface ScriptDefinition {
33950
34886
  description: string;
33951
34887
  /** When the Game Master should run it. */
33952
34888
  when?: string;
34889
+ /**
34890
+ * What in the story has it run after the Game Master tells it (opt in):
34891
+ * a check after each turn asks whether the turn met it, and runs the
34892
+ * script if so. For scripts the story doesn't wait on, like advancing a
34893
+ * clock or awarding XP, never ones that decide what happens.
34894
+ */
34895
+ after?: string;
33953
34896
  /** Who sees it run in play: the GM alone (the default) or the players too. */
33954
34897
  visibility?: "gm" | "player";
34898
+ /**
34899
+ * Where the card players see goes beside what the run did on the map:
34900
+ * "together" (the default) shows it with that moment, its result as the
34901
+ * blow lands; "card-first" shows the card first, then the map plays (a
34902
+ * table roll, then the bandits it called up stepping out).
34903
+ */
34904
+ cardOrder?: "together" | "card-first";
34905
+ /**
34906
+ * End the Game Master's turn as soon as this script has run (default
34907
+ * false): the GM writes nothing after it, and the player answers next.
34908
+ * For scripts that wait on the player, like a shop whose card they buy from.
34909
+ */
34910
+ waitsForPlayer?: boolean;
33955
34911
  /** The input fields, by name. */
33956
34912
  input?: { [field: string]: ScriptInputField };
33957
34913
  /** Runs the script. May be async; return plain JSON. */
33958
34914
  run(input: any, context: ScriptContext): unknown;
34915
+ /**
34916
+ * The script's own card: what players see where it ran (with visibility
34917
+ * "player") and the editor's test run shows. Gets run()'s result and the
34918
+ * input; returns a json-render spec, { root, elements }, whose props can
34919
+ * bind { "$state": "/result/..." }. Return null for the default card.
34920
+ */
34921
+ card?(result: any, input: any): ScriptCardSpec | null | undefined;
34922
+ /**
34923
+ * What the card's Buttons can run, by name. Each changes the game's files
34924
+ * when a player taps it and returns the card's next result; the card is
34925
+ * drawn again from it, for every player. Needs card and visibility "player".
34926
+ */
34927
+ actions?: { [name: string]: ScriptAction };
34928
+ }
34929
+
34930
+ /** What a press runs: one of the script's actions, or a change to the card's own state. */
34931
+ interface ScriptCardBinding {
34932
+ action: string;
34933
+ params?: { [param: string]: unknown };
34934
+ confirm?: { title: string; message: string; confirmLabel?: string; cancelLabel?: string; variant?: "default" | "danger" };
34935
+ onSuccess?: { set: { [statePath: string]: unknown } };
34936
+ onError?: { set: { [statePath: string]: unknown } };
34937
+ }
34938
+
34939
+ /** A json-render spec: the root element's key and every element by key. */
34940
+ interface ScriptCardSpec {
34941
+ root: string;
34942
+ elements: {
34943
+ [key: string]: {
34944
+ type: string;
34945
+ props?: { [prop: string]: unknown };
34946
+ children?: string[];
34947
+ /** What a tap runs: { press: { action: "buy", params: { item: "rope" } } }. */
34948
+ on?: { press: ScriptCardBinding | ScriptCardBinding[] };
34949
+ [key: string]: unknown;
34950
+ };
34951
+ };
34952
+ /** The card's own state as it starts, for picks a Button then confirms (not /result or /viewer). */
34953
+ state?: { [key: string]: unknown };
33959
34954
  }
33960
34955
 
33961
34956
  declare function defineScript(definition: ScriptDefinition): ScriptDefinition;
@@ -34359,6 +35354,34 @@ function renderGame(declared) {
34359
35354
  " * the way somewhere, at a place with no file, or outside a game.",
34360
35355
  " */",
34361
35356
  " currentLocation(options?: { include?: true | readonly string[] }): Promise<AnyFile | null>;",
35357
+ " /**",
35358
+ " * The character of whoever caused this run: who tapped the card's Button,",
35359
+ " * or whose message started the GM's turn. Null with no one (a test run",
35360
+ " * with no character picked), so check it.",
35361
+ " */",
35362
+ " player(options?: { include?: true | readonly string[] }): Promise<AnyFile | null>;",
35363
+ " /**",
35364
+ " * Rolls dice and shows the roll on the map and in the story, with its",
35365
+ ' * dice card, where the script made it: game.roll("1d20+5", { by: "Goblin 1",',
35366
+ ' * at: "Riven", reason: "Scimitar attack", against: { value: 14, label: "AC 14" } }).',
35367
+ " */",
35368
+ " roll(dice: string, options?: RollOptions): Promise<RollResult>;",
35369
+ " /**",
35370
+ " * Asks the player to roll for their own character, and answers with their",
35371
+ ' * dice as roll() does: game.askPlayerToRoll("1d20+5", { by: "Riven",',
35372
+ ' * at: "Goblin 1", reason: "Longsword attack", against: { value: 13, label: "AC 13" } }).',
35373
+ " * In a game the run stops here and the GM's reply ends with the dice in",
35374
+ " * front of the player; once they roll, the run starts over with the same",
35375
+ " * input, its earlier roll() calls give back the same dice, and this call",
35376
+ " * answers with the player's roll. Nothing the run did before it is kept",
35377
+ " * until then. Let a WAITING_FOR_PLAYER error through a catch. Where no",
35378
+ " * player is asked (a test run, the assistant) the dice are rolled for them.",
35379
+ " */",
35380
+ " askPlayerToRoll(dice: string, options?: RollOptions): Promise<RollResult>;",
35381
+ " /** Shows something landing on people with no roll: an auto-hit, a hazard, a spell's effect. */",
35382
+ " effect(options: EffectOptions): Promise<void>;",
35383
+ " /** Shows someone reacting: a bead over their head. */",
35384
+ " react(options: ReactOptions): Promise<void>;",
34362
35385
  " /** The world's file types. */",
34363
35386
  " fileTypes(): FileTypes;",
34364
35387
  " /**",
@@ -34715,7 +35738,7 @@ function coerceOptionalString(value) {
34715
35738
  return typeof value === "string" ? value : String(value);
34716
35739
  }
34717
35740
 
34718
- // ../../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
35741
+ // ../../node_modules/.bun/yaml@2.8.4/node_modules/yaml/dist/index.js
34719
35742
  var composer = require_composer();
34720
35743
  var Document = require_Document();
34721
35744
  var Schema = require_Schema();
@@ -37411,7 +38434,7 @@ async function refreshWorkspaceScriptTypes({
37411
38434
  // package.json
37412
38435
  var package_default = {
37413
38436
  name: "@craftrpgs/cli",
37414
- version: "0.2.0",
38437
+ version: "0.2.2-alpha.0",
37415
38438
  description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
37416
38439
  license: "UNLICENSED",
37417
38440
  homepage: "https://craftrpgs.com",
@@ -38490,7 +39513,7 @@ async function promptForBase({
38490
39513
  rl.close();
38491
39514
  }
38492
39515
  }
38493
- // ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/rng.js
39516
+ // ../../node_modules/.bun/uuid@11.1.1/node_modules/uuid/dist/esm/rng.js
38494
39517
  import { randomFillSync } from "crypto";
38495
39518
  var rnds8Pool = new Uint8Array(256);
38496
39519
  var poolPtr = rnds8Pool.length;
@@ -38502,7 +39525,7 @@ function rng() {
38502
39525
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
38503
39526
  }
38504
39527
 
38505
- // ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/stringify.js
39528
+ // ../../node_modules/.bun/uuid@11.1.1/node_modules/uuid/dist/esm/stringify.js
38506
39529
  var byteToHex = [];
38507
39530
  for (let i = 0;i < 256; ++i) {
38508
39531
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -38511,7 +39534,7 @@ function unsafeStringify(arr, offset = 0) {
38511
39534
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
38512
39535
  }
38513
39536
 
38514
- // ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/v7.js
39537
+ // ../../node_modules/.bun/uuid@11.1.1/node_modules/uuid/dist/esm/v7.js
38515
39538
  var _state = {};
38516
39539
  function v7(options, buf, offset) {
38517
39540
  let bytes;
@@ -40683,7 +41706,7 @@ var MAP_USAGE = `Usage: craft map get <location-path> [--json]
40683
41706
  ` + ` craft map set <location-path> --file <map.json>
40684
41707
  ` + `
40685
41708
  ` + "The map JSON is the stored MapData shape; in `set` input, areas/points may\n" + 'use `"link": "<workspace path | slug.type | referenceId>"` instead of\n' + `fileReferenceId, and element ids are minted when omitted. An element's
40686
- ` + '`exit` (a door, stairs, a road to another location) takes `"to"` as a\n' + "workspace path too; no `to` is the way out to the parent map. `get` prints\n" + `links and exit targets as workspace paths. Read
41709
+ ` + '`exit` (a door, stairs, a road to another location) takes `"to"` as a\n' + "workspace path too; no `to` is the way out to the parent map, and\n" + "`arriveAt` is the id of the pin or area it comes out at on that map. `get` prints\n" + `links and exit targets as workspace paths. Read
40687
41710
  ` + `.craft/instructions/authoring-maps.md for the format and
40688
41711
  ` + ".craft/instructions/linking-locations.md for exits.";
40689
41712
  async function runMap(argv) {
@@ -41749,6 +42772,12 @@ function collectAgentFileMentionAliases(markdown) {
41749
42772
  function stripDiacritics(value) {
41750
42773
  return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
41751
42774
  }
42775
+ function matchesOutsideMarkdownCode(markdown, regex) {
42776
+ return collectRegexMatchesOutsideProtectedRanges(markdown, regex);
42777
+ }
42778
+ function replaceOutsideMarkdownCode(markdown, regex, replace) {
42779
+ return replaceRegexMatchesOutsideProtectedRanges(markdown, regex, replace);
42780
+ }
41752
42781
  function collectRegexMatchesOutsideProtectedRanges(markdown, regex) {
41753
42782
  const protectedRanges = collectMarkdownProtectedRanges(markdown);
41754
42783
  const matches = [];
@@ -41764,6 +42793,26 @@ function collectRegexMatchesOutsideProtectedRanges(markdown, regex) {
41764
42793
  }
41765
42794
  return matches;
41766
42795
  }
42796
+ function replaceRegexMatchesOutsideProtectedRanges(markdown, regex, replace) {
42797
+ const protectedRanges = collectMarkdownProtectedRanges(markdown);
42798
+ let output = "";
42799
+ let lastIndex = 0;
42800
+ regex.lastIndex = 0;
42801
+ let match = regex.exec(markdown);
42802
+ while (match !== null) {
42803
+ const start = match.index;
42804
+ const end = start + match[0].length;
42805
+ if (rangeOverlapsAny({ start, end }, protectedRanges)) {
42806
+ match = regex.exec(markdown);
42807
+ continue;
42808
+ }
42809
+ output += markdown.slice(lastIndex, start);
42810
+ output += replace(match);
42811
+ lastIndex = end;
42812
+ match = regex.exec(markdown);
42813
+ }
42814
+ return output + markdown.slice(lastIndex);
42815
+ }
41767
42816
  function collectMarkdownProtectedRanges(markdown) {
41768
42817
  const ranges = [];
41769
42818
  collectFencedCodeRanges(markdown, ranges);
@@ -46122,15 +47171,111 @@ var sceneHeadingProps = object({
46122
47171
  });
46123
47172
 
46124
47173
  // ../shared/src/gm-narration/clean-narration-text.ts
46125
- var POSTPROCESSING_MARKER_RE = /(?:\n?---[ \t]*\n)?\s*\**-*\s*STARTING POSTPROCESSING[^\n]*/g;
47174
+ var POSTPROCESSING_MARKER = "STARTING POSTPROCESSING";
47175
+ var WHITESPACE_CHAR_RE = /\s/;
47176
+ var DIVIDER_LINE_END_RE = /[ \t]*\n/y;
46126
47177
  var TRANSLATED_POSTPROCESSING_MARKER_RE = /(?:\n?---[ \t]*\r?\n)?^[ \t]*(?:`{3,}[ \t]*)?\\?(?:-{3,}|\*{2})[ \t]*(?:正在)?(?:开始后处理|開始後處理)[ \t]*(?:-{3,}|\*{2})?[ \t]*\r?$/gm;
47178
+ function isWhitespaceAt(text, index) {
47179
+ const char = text[index];
47180
+ return char !== undefined && WHITESPACE_CHAR_RE.test(char);
47181
+ }
47182
+ function markerPrefixStart(text, floor, markerAt) {
47183
+ let start = markerAt;
47184
+ while (start > floor && isWhitespaceAt(text, start - 1)) {
47185
+ start--;
47186
+ }
47187
+ while (start > floor && text[start - 1] === "-") {
47188
+ start--;
47189
+ }
47190
+ while (start > floor && text[start - 1] === "*") {
47191
+ start--;
47192
+ }
47193
+ while (start > floor && isWhitespaceAt(text, start - 1)) {
47194
+ start--;
47195
+ }
47196
+ if (start - 3 >= floor && text.startsWith("---", start - 3)) {
47197
+ DIVIDER_LINE_END_RE.lastIndex = start;
47198
+ if (DIVIDER_LINE_END_RE.test(text)) {
47199
+ start -= 3;
47200
+ if (start > floor && text[start - 1] === `
47201
+ `) {
47202
+ start--;
47203
+ }
47204
+ }
47205
+ }
47206
+ return start;
47207
+ }
46127
47208
  function stripGmPostprocessingMarkers(text) {
46128
- return text.replace(POSTPROCESSING_MARKER_RE, "").replace(TRANSLATED_POSTPROCESSING_MARKER_RE, "");
47209
+ let stripped = "";
47210
+ let keepFrom = 0;
47211
+ let markerAt = text.indexOf(POSTPROCESSING_MARKER);
47212
+ while (markerAt >= 0) {
47213
+ stripped += text.slice(keepFrom, markerPrefixStart(text, keepFrom, markerAt));
47214
+ const lineEnd = text.indexOf(`
47215
+ `, markerAt + POSTPROCESSING_MARKER.length);
47216
+ keepFrom = lineEnd < 0 ? text.length : lineEnd;
47217
+ markerAt = text.indexOf(POSTPROCESSING_MARKER, keepFrom);
47218
+ }
47219
+ stripped += text.slice(keepFrom);
47220
+ return stripped.replace(TRANSLATED_POSTPROCESSING_MARKER_RE, "");
47221
+ }
47222
+ var HIDDEN_TAG_NAMES = "hidden-note|note-to-self|[a-z]+-hidden|hidden-[a-z]+|askPlayerToRoll|gmRollDice|askUserQuestion";
47223
+ var HIDDEN_NOTE_TAG_RE = new RegExp(`<(\\/?)(${HIDDEN_TAG_NAMES})>`, "gi");
47224
+ var HIDDEN_NOTE_OPEN_RE = new RegExp(`<(?:${HIDDEN_TAG_NAMES})>`, "i");
47225
+ var HIDDEN_NOTE_EDGE_TAGS_RE = new RegExp(`^<(?:${HIDDEN_TAG_NAMES})>|<\\/(?:${HIDDEN_TAG_NAMES})>$`, "gi");
47226
+
47227
+ // ../shared/src/gm-narration/narration-links.ts
47228
+ var NARRATION_LINK_RE = /!?\[([^[\]\n]{0,200}(?:\n[^[\]\n]{0,200})?)\]\([ \t]{0,16}(?:\n[ \t]{0,16})?(?:<([^<>\n]{0,256})>|([^\s()<>]{0,256}))(?:[ \t\n]{1,16}(?:"[^"\n]{0,200}"|'[^'\n]{0,200}'|\([^()\n]{0,200}\)))?[ \t]{0,16}(?:\n[ \t]{0,16})?\)/gu;
47229
+ var UNFINISHED_LINK_RE = /!?\[(?=[^[]*$)([^[\]\n]{0,200}(?:\n[^[\]\n]{0,200})?)(\](?:\([^)]{0,300})?)?$/gu;
47230
+ var LEFTOVER_TARGET_RE = /\]\([ \t]{0,16}<?[\p{L}\p{N}_-]{0,80}#[0-9a-fA-F]{6,8}>?(?:[ \t]{1,16}[^)\n]{0,200})?\)/gu;
47231
+ var WEB_TARGET_RE = /^https?:\/\//i;
47232
+ function narrationLinks(text, { streaming = false } = {}) {
47233
+ if (!text.includes("[")) {
47234
+ return [];
47235
+ }
47236
+ const links = matchesOutsideMarkdownCode(text, NARRATION_LINK_RE).map((match) => ({
47237
+ words: match[1] ?? "",
47238
+ target: (match[2] ?? match[3] ?? "").trim(),
47239
+ image: match[0].startsWith("!"),
47240
+ start: match.index,
47241
+ end: match.index + match[0].length
47242
+ }));
47243
+ const [tail] = matchesOutsideMarkdownCode(text, UNFINISHED_LINK_RE);
47244
+ if (tail && tail.index >= (links.at(-1)?.end ?? 0) && (tail[2] !== "]" || streaming)) {
47245
+ links.push({
47246
+ words: tail[1] ?? "",
47247
+ target: null,
47248
+ image: tail[0].startsWith("!"),
47249
+ start: tail.index,
47250
+ end: text.length
47251
+ });
47252
+ }
47253
+ return links;
47254
+ }
47255
+ function drawnAsWords(link) {
47256
+ return link.image || link.target === null || !WEB_TARGET_RE.test(link.target);
47257
+ }
47258
+ function narrationLinkWords(text) {
47259
+ let words = text;
47260
+ const links = narrationLinks(text).filter(drawnAsWords);
47261
+ if (links.length > 0) {
47262
+ words = "";
47263
+ let at = 0;
47264
+ for (const link of links) {
47265
+ words += text.slice(at, link.start) + link.words;
47266
+ at = link.end;
47267
+ }
47268
+ words += text.slice(at);
47269
+ }
47270
+ return words.includes("](") ? replaceOutsideMarkdownCode(words, LEFTOVER_TARGET_RE, () => "]") : words;
46129
47271
  }
47272
+ var NO_LINKS = new Map;
47273
+ var linksByMessage = new WeakMap;
46130
47274
 
46131
47275
  // ../shared/src/tts/text.ts
46132
47276
  function cleanTextForTTS(text) {
46133
- return stripGmPostprocessingMarkers(text).replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]+)\]\(.*?\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/(\*{3}|_{3})(.+?)\1/g, "$2").replace(/(\*{2}|_{2})(.+?)\1/g, "$2").replace(/(?<!\w)(\*|_)(.+?)\1(?!\w)/g, "$2").replace(/~~(.+?)~~/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\t ]*[-*+]\s+/gm, "").replace(/^[\t ]*\d+\.\s+/gm, "").replace(/<[^>]+>/g, "").replace(/\n{2,}/g, " ").replace(/\n/g, " ").replace(/ {2,}/g, " ").trim();
47277
+ const withoutPictures = stripGmPostprocessingMarkers(text).replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/!\[.*?\]\(.*?\)/g, "");
47278
+ return narrationLinkWords(withoutPictures).replace(/\[([^\]]+)\]\(.*?\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/(\*{3}|_{3})(.+?)\1/g, "$2").replace(/(\*{2}|_{2})(.+?)\1/g, "$2").replace(/(?<!\w)(\*|_)(.+?)\1(?!\w)/g, "$2").replace(/~~(.+?)~~/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\t ]*[-*+]\s+/gm, "").replace(/^[\t ]*\d+\.\s+/gm, "").replace(/<[^>]+>/g, "").replace(/\n{2,}/g, " ").replace(/\n/g, " ").replace(/ {2,}/g, " ").trim();
46134
47279
  }
46135
47280
  function hashTtsSegmentText(text) {
46136
47281
  let hash = 2166136261;