From 813f92d4199916a6a59e03a7805b0bb2697a6e0e Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 23 Aug 2026 11:53:17 -0400 Subject: [PATCH] The full telepath audit: every private glimpse held up, every silence voiced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A sweep of all 142 engine events against the client found three more telepath-class drops and eight silent chronicle gaps. THOUGHT STEAL's plunder and a kill's hand-spoils now get the reveal modal (generalized from the mind-read one — title, note, the cards fanned out); your own end-of-turn draw names its cards in the log. The mute events speak: dispels, walls of fire rising/burning/guttering, filled squares, crashing waterwalls, clattering thrown objects, and REUSE SPELL's retrieval. Also verified clean: every param-taking card has collection UI, and every cell/edge-targeting card is aimable on the board. Co-Authored-By: Claude Fable 5 --- packages/web/src/App.svelte | 45 +++++++++++++++++++++++++--------- packages/web/src/net.svelte.ts | 11 +++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index d75e003..6824634 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -54,9 +54,10 @@ let trapNotice = $state(null); /** A Ward sprung on YOU earns the same courtesy. */ let wardBite = $state<{ owner: string; amount: number } | null>(null); - /** A mind YOU read: the stolen glimpse arrives once, in an event — hold - * it up until dismissed, or it is lost to the scrollback. */ - let mindRead = $state<{ player: string; cards: CardInstance[] } | null>(null); + /** Cards revealed to YOU alone (a read mind, plunder, spoils): the + * glimpse arrives once, in an event — hold it up until dismissed, or it + * is lost to the scrollback. */ + let cardReveal = $state<{ title: string; note: string; cards: CardInstance[] } | null>(null); /** An easy-to-regret play awaiting its "are you sure?" (see prefs.cautions). */ let caution = $state<{ reasons: string[]; go: () => void } | null>(null); function withCaution(reasons: string[], go: () => void) { @@ -96,7 +97,27 @@ for (const e of events) { if (e.type === "trapSprung" && e.player === view.you && e.cardId) trapNotice = e.cardId; if (e.type === "handRevealedPrivate" && e.visibleTo === view.you) { - mindRead = { player: e.player, cards: e.cards }; + cardReveal = { + title: `${e.player}'s mind, laid bare`, + note: "Commit them to memory — the glimpse does not linger.", + cards: e.cards, + }; + } + if (e.type === "cardsStolenPrivate" && e.visibleTo === view.you) { + const pub = events.find((s) => s.type === "cardsStolen" && s.to === view!.you); + cardReveal = { + title: pub?.type === "cardsStolen" ? `Plucked from ${pub.from}'s thoughts` : "Plucked from their thoughts", + note: "Yours now — already in your hand.", + cards: e.cards, + }; + } + if (e.type === "handTakenPrivate" && e.visibleTo === view.you) { + const pub = events.find((s) => s.type === "handTaken" && s.to === view!.you); + cardReveal = { + title: pub?.type === "handTaken" ? `The spoils of ${pub.from}` : "The spoils", + note: "The fallen wizard's hand — already in yours.", + cards: e.cards, + }; } if (e.type === "wardSprung" && e.victim === view.you) { const dmg = events.find((d) => d.type === "damaged" && d.player === e.victim && d.source === "warded treasure"); @@ -1444,24 +1465,24 @@ {/if} - {#if mindRead} -
+ {#if cardReveal} +
-
{mindRead.player}'s mind, laid bare
+
{cardReveal.title}
- {#each mindRead.cards as card (card.instanceId)} + {#each cardReveal.cards as card (card.instanceId)} (faqCardId = id)} /> {/each} - {#if mindRead.cards.length === 0} + {#if cardReveal.cards.length === 0}
Nothing — their thoughts are empty-handed.
{/if}
- {mindRead.cards.length > 0 - ? `${mindRead.cards.length} card${mindRead.cards.length === 1 ? "" : "s"} — commit them to memory; the glimpse does not linger.` + {cardReveal.cards.length > 0 + ? `${cardReveal.cards.length} card${cardReveal.cards.length === 1 ? "" : "s"} — ${cardReveal.note}` : ""}
- +
{/if} diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index 1b27e55..04c9b6d 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -88,6 +88,17 @@ export function humanize(e: GameEvent): string | null { : `${e.player} wasn't holding that card — the erasure fizzles.`; case "cardsStolen": return `${e.to} steals ${e.count} card(s) from ${e.from}'s thoughts!`; case "handRevealed": return `${e.to} reads ${e.player}'s mind — their hand is revealed.`; + case "cardsDrawnPrivate": return e.cards.length > 0 + ? `You draw ${e.cards.map((c) => cardDef(c.cardId).name).join(", ")}.` + : null; + case "creationDispelled": return `The ${e.what.replace(/-/g, " ")} unravels — dispelled!`; + case "firewallCreated": return `A wall of fire roars up (${e.turns} turn${e.turns === 1 ? "" : "s"})!`; + case "firewallExpired": return `The wall of fire gutters out.`; + case "firewallBurned": return `${e.player} steps through the flames!`; + case "squareFilled": return `The square fills with ${e.kind === "stone" ? "solid stone" : e.kind === "slime" ? "green slime" : e.kind.replace(/-/g, " ")}!`; + case "waterwallCrashes": return `A wall of water crashes down!`; + case "objectThrown": return `The ${cardDef(e.cardId).name.toLowerCase()} clatters to the floor.`; + case "spellReused": return `${e.player}'s ${cardDef(e.card.cardId).name} leaps back to their hand!`; case "doorUnlocked": return `${e.player} unlocks a door.`; case "doorHeld": return `${e.player} holds the door open.`; case "doorReleased": return `The held door swings shut.`;