The table survives synthetic spell ids — fixes the blank screen
The Sticky Wand attaches its webs as a sustained effect with the
engine-internal id "sticky-web", which is not a printed card — and
cardDef throws on unknown ids. The scoresheet's new spell chips and
the chronicle's settles-over/wears-off lines both called it on
sustained ids, so the first webbing killed every render: mid-game the
actions bar died, and after a reload the whole table came up blank.
A spellName lookup now names synthetic effects properly ("Sticky
Wand webs") and falls back to the raw id rather than ever throwing,
and the chip peek only opens ids the card library can actually show.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
45803a2664
commit
3f3e6d9853
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { attentionLabel, net } from "./net.svelte";
|
||||
import { attentionLabel, net, spellName } from "./net.svelte";
|
||||
import Board from "./Board.svelte";
|
||||
import { PLAYER_COLORS, wizardColor } from "./colors";
|
||||
import Card from "./Card.svelte";
|
||||
@@ -432,6 +432,11 @@
|
||||
tacks: "handful-of-tacks", pit: "create-pit", safe: "safe",
|
||||
};
|
||||
|
||||
/** Sustained ids include engine constructs the card library cannot show. */
|
||||
function realCard(cardId: string): boolean {
|
||||
try { cardDef(cardId); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
function creatureStats(c: NonNullable<GameView["creatures"]>[number]): string {
|
||||
const hp = Number.isFinite(c.maxDamage)
|
||||
? `${c.maxDamage - c.damage} of ${c.maxDamage} hits left`
|
||||
@@ -1073,10 +1078,11 @@
|
||||
{/each}
|
||||
{#each spellsOn as e (e.id)}
|
||||
<button class="table-card spell-chip" onclick={() => {
|
||||
if (!realCard(e.cardId)) return;
|
||||
peekCard = { instanceId: `peek-${e.id}`, cardId: e.cardId };
|
||||
peekCreatureId = null;
|
||||
}}>
|
||||
✦ {cardDef(e.cardId).name} · {e.remainingTurns}
|
||||
✦ {spellName(e.cardId)} · {e.remainingTurns}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,20 @@ const SERVER_URL =
|
||||
? "ws://localhost:8787"
|
||||
: `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/ws`);
|
||||
|
||||
/** Sustained-effect ids that are engine constructs, not printed cards. */
|
||||
const SYNTHETIC_SPELL_NAMES: Record<string, string> = { "sticky-web": "Sticky Wand webs" };
|
||||
|
||||
/** Card name lookup that survives synthetic ids — render code must never throw. */
|
||||
export function spellName(cardId: string): string {
|
||||
const synthetic = SYNTHETIC_SPELL_NAMES[cardId];
|
||||
if (synthetic) return synthetic;
|
||||
try {
|
||||
return cardDef(cardId).name;
|
||||
} catch {
|
||||
return cardId;
|
||||
}
|
||||
}
|
||||
|
||||
export function humanize(e: GameEvent): string | null {
|
||||
switch (e.type) {
|
||||
case "gameStarted": return `Game started — ${e.players.join(", ")}. ${e.firstPlayer} goes first.`;
|
||||
@@ -43,8 +57,8 @@ export function humanize(e: GameEvent): string | null {
|
||||
: `${e.defender} is too small to hit — the attack misses!`;
|
||||
case "damageImmune": return `${e.player} is stone — the damage has no effect.`;
|
||||
case "lifeGained": return `${e.player} gains ${e.amount} life (${e.source}) — now ${e.lifeAfter}.`;
|
||||
case "spellSustained": return `${cardDef(e.cardId).name} settles over ${e.target} (${e.turns} turn${e.turns === 1 ? "" : "s"}).`;
|
||||
case "spellExpired": return `${cardDef(e.cardId).name} wears off ${e.target}.`;
|
||||
case "spellSustained": return `${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!`
|
||||
: `${e.player} is teleported away by ${e.by}!`;
|
||||
|
||||
Reference in New Issue
Block a user