@scrthq/runlog 0.0.42 → 0.0.44

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 (2) hide show
  1. package/dist/runlog.js +93 -11
  2. package/package.json +1 -1
package/dist/runlog.js CHANGED
@@ -26448,13 +26448,15 @@ var Action = external_exports.lazy(
26448
26448
  do: external_exports.literal("modCounter").describe("Adjust a counter."),
26449
26449
  counter: Id.describe("Id of the counter to change."),
26450
26450
  by: external_exports.number().int().optional().describe("Amount to add; negative subtracts."),
26451
- set: external_exports.number().int().optional().describe("Absolute value to set, overriding `by`.")
26451
+ set: external_exports.number().int().optional().describe("Absolute value to set, overriding `by`."),
26452
+ setFrom: external_exports.string().min(1).optional().describe("Set it to a value bound earlier \u2014 a roll's total or a prompt's answer \u2014 by name. Overrides `by` and `set`.")
26452
26453
  }).strict().describe("Adjust a counter."),
26453
26454
  external_exports.object({
26454
26455
  do: external_exports.literal("modResource").describe("Adjust a resource."),
26455
26456
  resource: Id.describe("Id of the resource to change."),
26456
26457
  by: external_exports.number().int().optional().describe("Amount to add; negative spends."),
26457
- set: external_exports.number().int().optional().describe("Absolute value to set, overriding `by`.")
26458
+ set: external_exports.number().int().optional().describe("Absolute value to set, overriding `by`."),
26459
+ setFrom: external_exports.string().min(1).optional().describe("Set it to a value bound earlier \u2014 a roll's total or a prompt's answer \u2014 by name. Overrides `by` and `set`.")
26458
26460
  }).strict().describe("Adjust a resource, clamped to its declared min and max."),
26459
26461
  external_exports.object({
26460
26462
  do: external_exports.literal("grantCard").describe("Draw cards into the player's hand."),
@@ -26520,7 +26522,10 @@ var EntryBase = {
26520
26522
  grants: external_exports.array(Id).optional().describe(
26521
26523
  "States applied to the relevant subject on resolution. Shorthand for an applyState trigger."
26522
26524
  ),
26523
- tags: external_exports.array(external_exports.string()).optional().describe("Free tags, for cross-referencing and for filtering results out in some modes.")
26525
+ tags: external_exports.array(external_exports.string()).optional().describe("Free tags, for cross-referencing and for filtering results out in some modes."),
26526
+ points: external_exports.number().int().min(0).optional().describe(
26527
+ "What a contestant earns for completing this result in a moderated mode. A result with points is a challenge the moderator can award; one without is an effect that lands on everyone."
26528
+ )
26524
26529
  };
26525
26530
  var LookupEntry = external_exports.object({
26526
26531
  ...EntryBase,
@@ -26640,7 +26645,8 @@ var Capability = external_exports.enum([
26640
26645
  "journal",
26641
26646
  "bandsResolution",
26642
26647
  "opposedResolution",
26643
- "keyedResolution"
26648
+ "keyedResolution",
26649
+ "moderated"
26644
26650
  ]).describe("An engine feature this pack needs in order to play correctly.");
26645
26651
  var Vocabulary = external_exports.object({
26646
26652
  run: external_exports.object({
@@ -26665,7 +26671,10 @@ var StateDef = external_exports.object({
26665
26671
  short: external_exports.string().max(8).optional().describe(
26666
26672
  "Compact form for embedding in the name of the thing itself \u2014 a file, a layer, a DAW track. Keep it very short, e.g. VF."
26667
26673
  ),
26668
- scope: external_exports.enum(["subject", "run"]).default("subject").describe("Whether this attaches to one subject or to the whole run."),
26674
+ scope: external_exports.enum(["subject", "run", "contestant"]).default("subject").describe(
26675
+ "Whether this attaches to one subject, to the whole run, or \u2014 in moderated play \u2014 to one contestant on the roster, marked by the moderator: spared from a curse, disqualified, and so on."
26676
+ ),
26677
+ until: external_exports.enum(["unitEnd"]).optional().describe("When the state lifts by itself. `unitEnd`: it is taken off everything it is on when the unit closes \u2014 a curse for this region, a cure for this region."),
26669
26678
  semantics: external_exports.array(
26670
26679
  external_exports.enum(["blocksEdit", "makesUntargetable", "excludesFromResult", "locksValue", "removesFromPlay"])
26671
26680
  ).optional().describe(
@@ -26855,6 +26864,16 @@ var Mode = external_exports.object({
26855
26864
  ).optional().describe(
26856
26865
  "Per-unit overrides, for modes with a fixed shape \u2014 'unit three always suffers a consequence', and the like."
26857
26866
  ),
26867
+ moderated: external_exports.object({
26868
+ contestants: external_exports.object({
26869
+ min: external_exports.number().int().min(1).default(2).describe("Fewest contestants."),
26870
+ max: external_exports.number().int().min(1).default(10).describe("Most contestants.")
26871
+ }).strict().default({ min: 2, max: 10 }).describe("How many people race. The moderator is not one of them."),
26872
+ award: external_exports.enum(["first", "everyone"]).default("first").describe("Whether only the first to finish a challenge scores it, or everyone who finishes does."),
26873
+ firstBonus: external_exports.number().int().min(0).default(0).describe("Extra points for finishing first, on top of the result's own. Meaningful with `everyone`.")
26874
+ }).strict().optional().describe(
26875
+ "Moderated play: one person runs the game on the device, a roster of named contestants races every drawn result, and the moderator awards points. Results with `points` are the challenges; everything else lands on everyone."
26876
+ ),
26858
26877
  notes: external_exports.array(external_exports.string()).optional().describe("Reminders shown to the player in this mode.")
26859
26878
  }).strict().describe(
26860
26879
  "A set of deltas over the base ruleset. Keeping modes as deltas rather than separate rulesets is what stops them drifting apart as the pack evolves."
@@ -27245,6 +27264,7 @@ function lintPack(pack) {
27245
27264
  let usesResources = resourceIds.size > 0;
27246
27265
  let usesTimers = false;
27247
27266
  let usesResolveTarget = false;
27267
+ const movedCounters = /* @__PURE__ */ new Set();
27248
27268
  for (const site of collectActionSites(pack)) {
27249
27269
  walkActions(site.actions, (a) => {
27250
27270
  switch (a.do) {
@@ -27263,6 +27283,7 @@ function lintPack(pack) {
27263
27283
  if (!counterIds.has(a.counter)) {
27264
27284
  d.push(err("ref/unknown-counter", site.path, `references unknown counter ${a.counter}`));
27265
27285
  }
27286
+ movedCounters.add(a.counter);
27266
27287
  break;
27267
27288
  case "modResource":
27268
27289
  if (!resourceIds.has(a.resource)) {
@@ -27371,6 +27392,7 @@ function lintPack(pack) {
27371
27392
  if (typeof item !== "string" && item.tally && !counterIds.has(item.tally)) {
27372
27393
  d.push(err("ref/unknown-counter", `${path}.${field}[${ii}]`, `tallies unknown counter ${item.tally}`));
27373
27394
  }
27395
+ if (typeof item !== "string" && item.tally) movedCounters.add(item.tally);
27374
27396
  if (typeof item !== "string" && item.shows && !tableIds.has(item.shows.table)) {
27375
27397
  d.push(err("ref/unknown-table", `${path}.${field}[${ii}]`, `shows unknown table ${item.shows.table}`));
27376
27398
  }
@@ -27479,8 +27501,8 @@ function lintPack(pack) {
27479
27501
  d.push(err("ref/unknown-state", path, `hooks unknown state ${sel.state}`));
27480
27502
  }
27481
27503
  }
27482
- if (!counter.incrementOn?.length && !counter.triggers?.length) {
27483
- d.push(warn("counter/inert", path, "counter has no incrementOn and no triggers; nothing will ever change it"));
27504
+ if (!counter.incrementOn?.length && !counter.triggers?.length && !movedCounters.has(counterId)) {
27505
+ d.push(warn("counter/inert", path, "counter has no incrementOn, no triggers, and nothing moves it; it will never change"));
27484
27506
  }
27485
27507
  }
27486
27508
  if (pack.targeting?.strategy === "anchoredOffset") {
@@ -27555,6 +27577,17 @@ function lintPack(pack) {
27555
27577
  );
27556
27578
  }
27557
27579
  };
27580
+ const moderatedModes = Object.values(pack.modes).filter((m) => m.moderated);
27581
+ const scoringEntries = Object.values(pack.tables).some((t) => t.entries.some((e) => (e.points ?? 0) > 0));
27582
+ requireCap("moderated", moderatedModes.length > 0, "has a moderated mode");
27583
+ if (moderatedModes.length > 0 && !scoringEntries) {
27584
+ d.push(warn("mode/nothing-to-award", "modes", "a mode is moderated, but no table entry carries points, so there is nothing to award"));
27585
+ }
27586
+ for (const [modeId, m] of Object.entries(pack.modes)) {
27587
+ if (m.moderated && m.moderated.contestants.max < m.moderated.contestants.min) {
27588
+ d.push(err("mode/contestant-range", `modes.${modeId}.moderated.contestants`, `max (${m.moderated.contestants.max}) is below min (${m.moderated.contestants.min})`));
27589
+ }
27590
+ }
27558
27591
  requireCap("decks", usesDecks, "uses decks");
27559
27592
  requireCap("resources", usesResources, "uses resources");
27560
27593
  requireCap("counters", counterIds.size > 0, "declares counters");
@@ -27613,7 +27646,8 @@ var IMPLEMENTED_CAPABILITIES = [
27613
27646
  "journal",
27614
27647
  "bandsResolution",
27615
27648
  "opposedResolution",
27616
- "keyedResolution"
27649
+ "keyedResolution",
27650
+ "moderated"
27617
27651
  ];
27618
27652
  function parsePack(input2) {
27619
27653
  if (typeof input2 !== "object" || input2 === null) {
@@ -28106,6 +28140,11 @@ function modeLength(pack, mode) {
28106
28140
  return `as many ${n.units} as you like${max ? `, up to ${max}` : ""}`;
28107
28141
  }
28108
28142
  function modePlayers(mode) {
28143
+ const m = mode.moderated;
28144
+ if (m) {
28145
+ const n = m.contestants.min === m.contestants.max ? `${m.contestants.min}` : `${m.contestants.min}\u2013${m.contestants.max}`;
28146
+ return `moderated, ${n} contestants, ${m.award === "everyone" ? "everyone who finishes scores" : "first to finish scores"}${m.firstBonus ? ` (+${m.firstBonus} for first)` : ""}`;
28147
+ }
28109
28148
  const p = mode.players;
28110
28149
  if (!p || p.min <= 1 && p.max <= 1) return "solo";
28111
28150
  return p.min === p.max ? `${p.min} players` : `${p.min}\u2013${p.max} players`;
@@ -28294,7 +28333,8 @@ function tableRows(pack, table, opts) {
28294
28333
  if (e.requires?.length) extras.push(`Only if ${conditionsInWords(pack, e.requires)}; otherwise roll again.`);
28295
28334
  }
28296
28335
  const text = [e.title ? `${e.title}. ` : "", sentence(e.text), ...extras.map((x) => ` ${x}`)].join("");
28297
- return [keys[i] ?? "", text];
28336
+ const pts = e.points !== void 0 && e.points > 0 ? `${keys[i] ?? ""} \xB7 ${e.points} pt${e.points === 1 ? "" : "s"}` : keys[i] ?? "";
28337
+ return [pts, text];
28298
28338
  });
28299
28339
  }
28300
28340
  function license(pack) {
@@ -28390,8 +28430,8 @@ function rulebookDoc(pack) {
28390
28430
  ["State", "On", "Means"],
28391
28431
  states.map(([, s]) => [
28392
28432
  s.short ? `${s.label} (${s.short})` : s.label,
28393
- s.scope === "run" ? `the ${n.run}` : `a ${n.subject}`,
28394
- [s.description ?? "", ...(s.semantics ?? []).map(semanticInWords), s.group ? `Replaces any other \u201C${s.group}\u201D state.` : ""].filter(Boolean).join(" ")
28433
+ s.scope === "run" ? `the ${n.run}` : s.scope === "contestant" ? "a contestant" : `a ${n.subject}`,
28434
+ [s.description ?? "", ...(s.semantics ?? []).map(semanticInWords), s.group ? `Replaces any other \u201C${s.group}\u201D state.` : "", s.until === "unitEnd" ? `Lifts when the ${n.unit} closes.` : ""].filter(Boolean).join(" ")
28395
28435
  ])
28396
28436
  );
28397
28437
  }
@@ -28777,6 +28817,8 @@ function initialState(pack, e) {
28777
28817
  outcomes: [],
28778
28818
  obligations: [],
28779
28819
  firedOnce: [],
28820
+ contestants: [],
28821
+ awards: [],
28780
28822
  ending: null
28781
28823
  };
28782
28824
  }
@@ -28951,6 +28993,10 @@ function reduce(pack, log) {
28951
28993
  for (const subject of state.subjects) {
28952
28994
  if (subject.unit === state.unit) subject.finalized = true;
28953
28995
  }
28996
+ const lifts = (id) => pack.states?.[id]?.until === "unitEnd";
28997
+ state.runStates = state.runStates.filter((id) => !lifts(id));
28998
+ for (const subject of state.subjects) subject.states = subject.states.filter((id) => !lifts(id));
28999
+ for (const who of state.contestants) who.states = who.states.filter((id) => !lifts(id));
28954
29000
  break;
28955
29001
  }
28956
29002
  case "JournalWritten":
@@ -28984,6 +29030,42 @@ function reduce(pack, log) {
28984
29030
  state.status = "ended";
28985
29031
  state.ending = event.ending;
28986
29032
  break;
29033
+ case "ContestantAdded":
29034
+ if (!state.contestants.some((c) => c.id === event.contestant)) {
29035
+ state.contestants.push({ id: event.contestant, name: event.name, states: [] });
29036
+ }
29037
+ break;
29038
+ case "ContestantRemoved":
29039
+ state.contestants = state.contestants.filter((c) => c.id !== event.contestant);
29040
+ break;
29041
+ case "ContestantStateApplied": {
29042
+ const who = state.contestants.find((c) => c.id === event.contestant);
29043
+ if (!who) break;
29044
+ const group = pack.states?.[event.state]?.group;
29045
+ if (group) who.states = who.states.filter((s) => s === event.state || pack.states?.[s]?.group !== group);
29046
+ if (!who.states.includes(event.state)) who.states.push(event.state);
29047
+ break;
29048
+ }
29049
+ case "ContestantStateRemoved": {
29050
+ const who = state.contestants.find((c) => c.id === event.contestant);
29051
+ if (who) who.states = who.states.filter((s) => s !== event.state);
29052
+ break;
29053
+ }
29054
+ case "Awarded":
29055
+ if (!state.awards.some((a) => a.contestant === event.contestant && a.outcome === event.outcome)) {
29056
+ state.awards.push({
29057
+ contestant: event.contestant,
29058
+ outcome: event.outcome,
29059
+ table: event.table,
29060
+ entryId: event.entryId,
29061
+ points: event.points,
29062
+ at: event.at
29063
+ });
29064
+ }
29065
+ break;
29066
+ case "AwardRevoked":
29067
+ state.awards = state.awards.filter((a) => !(a.contestant === event.contestant && a.outcome === event.outcome));
29068
+ break;
28987
29069
  case "Checked": {
28988
29070
  const key = `${event.step}|${event.item}`;
28989
29071
  if (event.on) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrthq/runlog",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "Validate, lint, bundle and test Runlog rule packs.",
5
5
  "type": "module",
6
6
  "license": "MIT",