@storylet-studio/runtime 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -72,6 +72,9 @@ interface OutcomeView {
72
72
  gameId: string;
73
73
  title?: string;
74
74
  purpose?: string;
75
+ /** The outcome's fields, exactly as the bundle carries them: game data
76
+ * declared by the box's `outcomeFields`, never read by the engine. */
77
+ fields?: Record<string, ScalarValue>;
75
78
  /** Evaluated against CURRENT state at the moment of the ask. */
76
79
  available: boolean;
77
80
  }
@@ -130,7 +133,8 @@ type TraceEvent = {
130
133
  card: string;
131
134
  reason: TraceVerdict | "hand-condition" | "vanished";
132
135
  }
133
- /** Card and outcome gameIds. */
136
+ /** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
137
+ * played with none. */
134
138
  | {
135
139
  type: "play";
136
140
  card: string;
@@ -657,7 +661,16 @@ declare class Flow {
657
661
  outcomes(cardId: string, from: string): OutcomeView[];
658
662
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
659
663
  * board (you never play a card from inside the deck). Throws before any
660
- * mutation on a gated-shut outcome or a bad write target. */
664
+ * mutation on a gated-shut outcome or a bad write target.
665
+ *
666
+ * A card with NO outcomes is played with none, named as "" (the
667
+ * no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,
668
+ * whose play means "shown". It is everything a play is except the writes:
669
+ * the play log and the history functions count it, the box's turn moves by
670
+ * the usual rule, the redraw rests it and it leaves its hand. "" is the one
671
+ * spelling in all four runtimes, because a Blueprint pin cannot be absent.
672
+ * Only the empty-for-empty case is new: "" on a card that has outcomes is
673
+ * refused, and a named outcome on a card with none is refused as before. */
661
674
  play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
662
675
  /** One owned property's address, owner segment and all (4.4). */
663
676
  private address;
package/dist/index.d.ts CHANGED
@@ -72,6 +72,9 @@ interface OutcomeView {
72
72
  gameId: string;
73
73
  title?: string;
74
74
  purpose?: string;
75
+ /** The outcome's fields, exactly as the bundle carries them: game data
76
+ * declared by the box's `outcomeFields`, never read by the engine. */
77
+ fields?: Record<string, ScalarValue>;
75
78
  /** Evaluated against CURRENT state at the moment of the ask. */
76
79
  available: boolean;
77
80
  }
@@ -130,7 +133,8 @@ type TraceEvent = {
130
133
  card: string;
131
134
  reason: TraceVerdict | "hand-condition" | "vanished";
132
135
  }
133
- /** Card and outcome gameIds. */
136
+ /** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
137
+ * played with none. */
134
138
  | {
135
139
  type: "play";
136
140
  card: string;
@@ -657,7 +661,16 @@ declare class Flow {
657
661
  outcomes(cardId: string, from: string): OutcomeView[];
658
662
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
659
663
  * board (you never play a card from inside the deck). Throws before any
660
- * mutation on a gated-shut outcome or a bad write target. */
664
+ * mutation on a gated-shut outcome or a bad write target.
665
+ *
666
+ * A card with NO outcomes is played with none, named as "" (the
667
+ * no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,
668
+ * whose play means "shown". It is everything a play is except the writes:
669
+ * the play log and the history functions count it, the box's turn moves by
670
+ * the usual rule, the redraw rests it and it leaves its hand. "" is the one
671
+ * spelling in all four runtimes, because a Blueprint pin cannot be absent.
672
+ * Only the empty-for-empty case is new: "" on a card that has outcomes is
673
+ * refused, and a named outcome on a card with none is refused as before. */
661
674
  play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
662
675
  /** One owned property's address, owner segment and all (4.4). */
663
676
  private address;
package/dist/index.js CHANGED
@@ -2054,33 +2054,48 @@ var Flow = class {
2054
2054
  gameId: effectiveGameId(o),
2055
2055
  ...o.title !== void 0 ? { title: o.title } : {},
2056
2056
  ...o.purpose !== void 0 ? { purpose: o.purpose } : {},
2057
+ ...o.fields !== void 0 ? { fields: o.fields } : {},
2057
2058
  available: this.passes(o.condition, ctx)
2058
2059
  }));
2059
2060
  }
2060
2061
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
2061
2062
  * board (you never play a card from inside the deck). Throws before any
2062
- * mutation on a gated-shut outcome or a bad write target. */
2063
+ * mutation on a gated-shut outcome or a bad write target.
2064
+ *
2065
+ * A card with NO outcomes is played with none, named as "" (the
2066
+ * no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,
2067
+ * whose play means "shown". It is everything a play is except the writes:
2068
+ * the play log and the history functions count it, the box's turn moves by
2069
+ * the usual rule, the redraw rests it and it leaves its hand. "" is the one
2070
+ * spelling in all four runtimes, because a Blueprint pin cannot be absent.
2071
+ * Only the empty-for-empty case is new: "" on a card that has outcomes is
2072
+ * refused, and a named outcome on a card with none is refused as before. */
2063
2073
  play(cardId, outcomeGameId, from, opts = {}) {
2064
2074
  this.assertOpen();
2065
2075
  const { entry, ask } = this.resolveDealt(cardId, from);
2066
- const outcome = entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);
2067
- if (!outcome) throw new Error(`card "${effectiveGameId(entry.card)}" has no outcome "${outcomeGameId}"`);
2076
+ const bare = outcomeGameId === "";
2077
+ if (bare && entry.card.outcomes.length > 0) {
2078
+ throw new Error(`card "${effectiveGameId(entry.card)}" has outcomes (${entry.card.outcomes.map((o) => effectiveGameId(o)).join(", ")}); name the one played`);
2079
+ }
2080
+ const outcome = bare ? void 0 : entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);
2081
+ if (!bare && !outcome) throw new Error(`card "${effectiveGameId(entry.card)}" has no outcome "${outcomeGameId}"`);
2068
2082
  const handEnv = this.buildHandEnv(ask);
2069
2083
  const ctx = this.evalCtx(entry.box, entry.deck, handEnv);
2070
- if (!this.passes(outcome.condition, ctx)) {
2084
+ if (outcome && !this.passes(outcome.condition, ctx)) {
2071
2085
  throw new Error(`outcome "${outcomeGameId}" on "${effectiveGameId(entry.card)}" is gated shut`);
2072
2086
  }
2073
2087
  const perPlay = entry.box.turn !== void 0 ? 0 : this.internals.bundle.settings.playAdvancesTurns;
2074
2088
  const newTurn = (this.turnCounts.get(entry.box.id) ?? 0) + (opts.advanceTurns ?? perPlay);
2075
2089
  const writes = [];
2076
- for (const [target, expr] of Object.entries(outcome.changes)) {
2090
+ for (const [target, expr] of Object.entries(outcome?.changes ?? {})) {
2077
2091
  writes.push({ target, value: this.eval(expr, ctx) });
2078
2092
  }
2079
2093
  for (const { target, value } of writes) {
2080
2094
  const { path, prev } = this.applyWrite(target, value, entry, handEnv);
2081
2095
  if (this.tracing) this.emit({ type: "write", target, path, value, ...prev !== void 0 ? { prev } : {} }, newTurn);
2082
2096
  }
2083
- const record = { card: effectiveGameId(entry.card), outcome: effectiveGameId(outcome), turn: newTurn };
2097
+ const outcomeId = outcome ? effectiveGameId(outcome) : "";
2098
+ const record = { card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn };
2084
2099
  this.playLog.push(record);
2085
2100
  this.indexPlay(record);
2086
2101
  if (entry.card.redraw === "never") {
@@ -2095,7 +2110,7 @@ var Flow = class {
2095
2110
  (this.boardContents.get(handId) ?? []).filter((id) => id !== entry.card.id)
2096
2111
  );
2097
2112
  this.turnCounts.set(entry.box.id, newTurn);
2098
- if (this.tracing) this.emit({ type: "play", card: effectiveGameId(entry.card), outcome: effectiveGameId(outcome), turn: newTurn }, newTurn);
2113
+ if (this.tracing) this.emit({ type: "play", card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn }, newTurn);
2099
2114
  }
2100
2115
  /** One owned property's address, owner segment and all (4.4). */
2101
2116
  address(kind, id) {