From 071e7ce988906587e2cbee74641a9f1172da6faf Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 21 Sep 2026 11:09:01 -0400 Subject: [PATCH] A permanent spell settles 'for good' in the chronicle and the receipt, not for a billion turns Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG --- packages/web/src/net.svelte.ts | 6 ++++-- packages/web/src/receipt.ts | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index f4ea00f..ee9f13e 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -1,7 +1,7 @@ // Websocket client + reactive session state (Svelte 5 runes). import type { Command, GameEvent, GameView, Side } from "@wizwar/engine"; -import { cardDef } from "@wizwar/engine"; +import { cardDef, isPermanentDuration } from "@wizwar/engine"; import { receiptFor } from "./receipt"; /** The same server over plain HTTP, for the one door a socket cannot carry: a picture. */ @@ -99,7 +99,9 @@ export function humanize(e: GameEvent): string | null { case "squareContentDestroyed": return e.kind === "ooze" ? `🔥 The ooze burns away under ${e.attacker}'s fire!` : `🌿 The ${e.kind} is torn apart by ${e.attacker}'s attack!`; case "slowDeathWindow": return `Slow Death bites ${e.player} for ${e.points} — a counter hovers over the wound…`; case "slowDeathCountered": return `${e.player}'s ${cardDef(e.cardId).name} blunts the rot — ${e.remaining} point${e.remaining === 1 ? "" : "s"} still coming.`; - case "spellSustained": return `${spellName(e.cardId)} settles over ${e.target} (${e.turns} turn${e.turns === 1 ? "" : "s"}).`; + case "spellSustained": return isPermanentDuration(e.turns) + ? `${spellName(e.cardId)} settles over ${e.target} for good.` + : `${spellName(e.cardId)} settles over ${e.target} (${e.turns} turn${e.turns === 1 ? "" : "s"}).`; case "spellExpired": return `${spellName(e.cardId)} wears off ${e.target}.`; case "teleported": return e.by === e.player ? `${e.player} teleports across the maze!` diff --git a/packages/web/src/receipt.ts b/packages/web/src/receipt.ts index 8c5ed42..a808a78 100644 --- a/packages/web/src/receipt.ts +++ b/packages/web/src/receipt.ts @@ -3,7 +3,7 @@ // engine's own events, so it explains what happened rather than what the // cards promise. -import { cardDef, type GameEvent } from "@wizwar/engine"; +import { cardDef, isPermanentDuration, type GameEvent } from "@wizwar/engine"; export interface Receipt { title: string; @@ -75,7 +75,8 @@ export function receiptFor(batch: readonly GameEvent[], e: GameEvent): Receipt | : e.fullyStopped ? "nothing lands" : healed && healed.type === "lifeGained" ? `${healed.amount} heals ${e.defender} instead` : e.damageDealt > 0 ? `${e.damageDealt} lands on ${e.defender}` - : tookHold && tookHold.type === "spellSustained" ? `${card} takes hold of ${e.defender} for ${tookHold.turns} turn${tookHold.turns === 1 ? "" : "s"}` + : tookHold && tookHold.type === "spellSustained" + ? (isPermanentDuration(tookHold.turns) ? `${card} takes hold of ${e.defender} for good` : `${card} takes hold of ${e.defender} for ${tookHold.turns} turn${tookHold.turns === 1 ? "" : "s"}`) : "nothing to land", ); lines.push(parts.join(" → "));