@storylet-studio/runtime 0.6.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
@@ -133,7 +133,8 @@ type TraceEvent = {
133
133
  card: string;
134
134
  reason: TraceVerdict | "hand-condition" | "vanished";
135
135
  }
136
- /** Card and outcome gameIds. */
136
+ /** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
137
+ * played with none. */
137
138
  | {
138
139
  type: "play";
139
140
  card: string;
@@ -660,7 +661,16 @@ declare class Flow {
660
661
  outcomes(cardId: string, from: string): OutcomeView[];
661
662
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
662
663
  * board (you never play a card from inside the deck). Throws before any
663
- * 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. */
664
674
  play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
665
675
  /** One owned property's address, owner segment and all (4.4). */
666
676
  private address;
package/dist/index.d.ts CHANGED
@@ -133,7 +133,8 @@ type TraceEvent = {
133
133
  card: string;
134
134
  reason: TraceVerdict | "hand-condition" | "vanished";
135
135
  }
136
- /** Card and outcome gameIds. */
136
+ /** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
137
+ * played with none. */
137
138
  | {
138
139
  type: "play";
139
140
  card: string;
@@ -660,7 +661,16 @@ declare class Flow {
660
661
  outcomes(cardId: string, from: string): OutcomeView[];
661
662
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
662
663
  * board (you never play a card from inside the deck). Throws before any
663
- * 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. */
664
674
  play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
665
675
  /** One owned property's address, owner segment and all (4.4). */
666
676
  private address;
package/dist/index.js CHANGED
@@ -2060,28 +2060,42 @@ var Flow = class {
2060
2060
  }
2061
2061
  /** Apply an outcome (schema 3.7): the card must sit in a hand on the
2062
2062
  * board (you never play a card from inside the deck). Throws before any
2063
- * 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. */
2064
2073
  play(cardId, outcomeGameId, from, opts = {}) {
2065
2074
  this.assertOpen();
2066
2075
  const { entry, ask } = this.resolveDealt(cardId, from);
2067
- const outcome = entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);
2068
- 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}"`);
2069
2082
  const handEnv = this.buildHandEnv(ask);
2070
2083
  const ctx = this.evalCtx(entry.box, entry.deck, handEnv);
2071
- if (!this.passes(outcome.condition, ctx)) {
2084
+ if (outcome && !this.passes(outcome.condition, ctx)) {
2072
2085
  throw new Error(`outcome "${outcomeGameId}" on "${effectiveGameId(entry.card)}" is gated shut`);
2073
2086
  }
2074
2087
  const perPlay = entry.box.turn !== void 0 ? 0 : this.internals.bundle.settings.playAdvancesTurns;
2075
2088
  const newTurn = (this.turnCounts.get(entry.box.id) ?? 0) + (opts.advanceTurns ?? perPlay);
2076
2089
  const writes = [];
2077
- for (const [target, expr] of Object.entries(outcome.changes)) {
2090
+ for (const [target, expr] of Object.entries(outcome?.changes ?? {})) {
2078
2091
  writes.push({ target, value: this.eval(expr, ctx) });
2079
2092
  }
2080
2093
  for (const { target, value } of writes) {
2081
2094
  const { path, prev } = this.applyWrite(target, value, entry, handEnv);
2082
2095
  if (this.tracing) this.emit({ type: "write", target, path, value, ...prev !== void 0 ? { prev } : {} }, newTurn);
2083
2096
  }
2084
- 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 };
2085
2099
  this.playLog.push(record);
2086
2100
  this.indexPlay(record);
2087
2101
  if (entry.card.redraw === "never") {
@@ -2096,7 +2110,7 @@ var Flow = class {
2096
2110
  (this.boardContents.get(handId) ?? []).filter((id) => id !== entry.card.id)
2097
2111
  );
2098
2112
  this.turnCounts.set(entry.box.id, newTurn);
2099
- 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);
2100
2114
  }
2101
2115
  /** One owned property's address, owner segment and all (4.4). */
2102
2116
  address(kind, id) {