From e412463aae852c0350c30fa321444ea456f99acf Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 17 Aug 2026 23:09:40 -0400 Subject: [PATCH] Thumb of God gets its ceremony: prompt, sighted dimming, and the die MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cast always worked (click a square), but nothing guided it and nothing marked its arrival. Now: a targeting prompt, eligibility dimming to the caster's sighted squares (the engine demands LOS to the aim point), and a flourish — the aiming mark, a shadow gathering, the die crashing down out of a clear sky with a bounce and flung grit, a drift line when fate moves it, and every scattered token streaking to where it lands. In the /?fx gallery as die-drop. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc --- packages/engine/src/view.ts | 4 + packages/web/src/App.svelte | 1 + packages/web/src/FxGallery.svelte | 1 + packages/web/src/fx-sprites/DieDrop.svelte | 122 +++++++++++++++++++++ packages/web/src/fx-sprites/index.ts | 2 + packages/web/src/fx.ts | 11 +- 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/fx-sprites/DieDrop.svelte diff --git a/packages/engine/src/view.ts b/packages/engine/src/view.ts index 02d63d0..c87b03e 100644 --- a/packages/engine/src/view.ts +++ b/packages/engine/src/view.ts @@ -352,6 +352,10 @@ export function eligibleCellsFor(view: GameView, cardId: string): Set | return out; } + if (cardId === "thumb-of-god") { + // The die is aimed by sight; where it lands after drifting is fate's. + return sighted; + } if (cardId === "stone-to-water") { // The cast demands sight of the stone block, so only sighted ones light. // The card equally targets stone WALLS — edges the cell-shadow cannot diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index b9b45c2..c3b41c8 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -271,6 +271,7 @@ "create-door": "click a wall for the new door", "warp-wand": "click a wall to warp it open", "stone-to-water": "click a stone wall, or a stone-filled square", + "thumb-of-god": "aim the die at a square in sight — it may drift on impact", "dispel-creation": "click the creation — a square, a creature, or a conjured wall", }; const EDGE_CARDS = new Set([ diff --git a/packages/web/src/FxGallery.svelte b/packages/web/src/FxGallery.svelte index 20fc468..268d927 100644 --- a/packages/web/src/FxGallery.svelte +++ b/packages/web/src/FxGallery.svelte @@ -34,6 +34,7 @@ { kind: "soul", at: { x: 1, y: 2 } }, { kind: "fireworks", at: mid }, { kind: "chaos-swirl", at: mid }, + { kind: "die-drop", at: { x: 1, y: 1 }, aim: { x: 0, y: 2 } }, { kind: "pit-fall", at: mid }, { kind: "ooze-slip", at: mid }, { kind: "tacks-ow", at: mid }, diff --git a/packages/web/src/fx-sprites/DieDrop.svelte b/packages/web/src/fx-sprites/DieDrop.svelte new file mode 100644 index 0000000..8bc89c2 --- /dev/null +++ b/packages/web/src/fx-sprites/DieDrop.svelte @@ -0,0 +1,122 @@ + + + + +{#if drifted} + +{/if} + + + + + + + + + + + + + + +{#each DEBRIS as deg, i (deg)} + +{/each} + + diff --git a/packages/web/src/fx-sprites/index.ts b/packages/web/src/fx-sprites/index.ts index 24364a8..7fa9685 100644 --- a/packages/web/src/fx-sprites/index.ts +++ b/packages/web/src/fx-sprites/index.ts @@ -8,6 +8,7 @@ import Bolt from "./Bolt.svelte"; import Burst from "./Burst.svelte"; import ChaosSwirl from "./ChaosSwirl.svelte"; import Claw from "./Claw.svelte"; +import DieDrop from "./DieDrop.svelte"; import DustPuff from "./DustPuff.svelte"; import Fireball from "./Fireball.svelte"; import Fireworks from "./Fireworks.svelte"; @@ -49,6 +50,7 @@ export const FX_SPRITES: { [K in BoardFx["kind"]]: Component<{ fx: FxOf }> } soul: Soul, fireworks: Fireworks, "chaos-swirl": ChaosSwirl, + "die-drop": DieDrop, "pit-fall": PitFall, "ooze-slip": OozeSlip, "tacks-ow": TacksOw, diff --git a/packages/web/src/fx.ts b/packages/web/src/fx.ts index b018bda..c35ab42 100644 --- a/packages/web/src/fx.ts +++ b/packages/web/src/fx.ts @@ -22,7 +22,8 @@ export type FxShape = | { kind: "portal"; cell: Cell; side: Side } | { kind: "sector-spin"; origin: Cell; clockwise: boolean } | { kind: "sector-slide"; from: Cell; to: Cell } - | { kind: "edge-dust"; cell: Cell; side: Side }; + | { kind: "edge-dust"; cell: Cell; side: Side } + | { kind: "die-drop"; at: Cell; aim: Cell }; export type BoardFx = FxShape & { id: number }; /** The narrow type of one effect kind (for sprite components). */ export type FxOf = BoardFx & { kind: K }; @@ -37,6 +38,7 @@ export function fxTtl(kind: BoardFx["kind"]): number { case "portal": case "portal-cell": case "soul": case "fireworks": case "chaos-swirl": case "sector-spin": case "sector-slide": return 1500; + case "die-drop": return 1600; default: return 900; } } @@ -271,6 +273,13 @@ export function fxForEvents( push({ kind: "pit-fall", at: e.at }); beat++; break; + case "thumbOfGod": + push({ kind: "die-drop", at: e.landedAt, aim: e.aimedAt }); + beat += 2; // the scattered tokens' streaks follow the impact + break; + case "tokenScattered": + push({ kind: "streak", a: cellMid(e.from), b: cellMid(e.to) }); + break; case "climbedFromPit": { if (e.success) { const at = posOf(e.player);