From bcaa06251adc53bb2d06453b2f1bbd057f2fb7d7 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 17 Aug 2026 10:30:48 -0400 Subject: [PATCH] The review lands: twelve sprites get their motion stories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The outside review's diagnosis was right — the weak sprites had one element doing one motion. Now each tells its story: the whiff is three air streaks sweeping past a dodger with a wobble; the hit is a contact flash, a compressed ring punched outward, and flying debris; the streak's head visibly travels, drawing a tapered trail that collapses into the destination; the sparkle snaps white-hot between counter-rotating glints and sheds motes; the tacks are drawn silhouettes now, points up as a tack on the floor menaces, hopping in sequence; the claw slashes reveal one by one along their length with a bright leading edge while the whole strike rakes sideways. The sector ghosts keep their dashed frames but earn motion arcs, directional chevrons, corner dust, and a grinding shudder as they settle. The portal opens wide, ripples along its length, and pinches shut; cell mouths collapse rings of energy inward. The thornbush springs actual branches with thorn spurs. The slime is an asymmetric blob that grips, with elastic strands snapping toward the caught. Dust gains contrast, varied grain, and blows perpendicular to the wall that shed it. Co-Authored-By: Claude Fable 5 --- packages/web/src/fx-sprites/Claw.svelte | 53 +++++-- packages/web/src/fx-sprites/DustPuff.svelte | 53 ++++--- packages/web/src/fx-sprites/Hit.svelte | 56 +++++++- packages/web/src/fx-sprites/Portal.svelte | 58 +++++--- .../web/src/fx-sprites/SectorGrind.svelte | 132 ++++++++++++++---- packages/web/src/fx-sprites/SlimeStuck.svelte | 52 +++++-- packages/web/src/fx-sprites/Sparkle.svelte | 54 +++++-- packages/web/src/fx-sprites/Streak.svelte | 49 ++++--- packages/web/src/fx-sprites/TacksOw.svelte | 47 ++++--- packages/web/src/fx-sprites/ThornSnap.svelte | 54 +++++-- packages/web/src/fx-sprites/Whiff.svelte | 35 +++-- 11 files changed, 474 insertions(+), 169 deletions(-) diff --git a/packages/web/src/fx-sprites/Claw.svelte b/packages/web/src/fx-sprites/Claw.svelte index ce6c0ba..d584998 100644 --- a/packages/web/src/fx-sprites/Claw.svelte +++ b/packages/web/src/fx-sprites/Claw.svelte @@ -5,6 +5,11 @@ let { fx }: { fx: FxOf<"claw"> } = $props(); const uid = $props.id(); const c = $derived(center(fx.at)); + const GASHES = [ + { x: -12, cls: "g0" }, + { x: -3, cls: "g1" }, + { x: 6, cls: "g2" }, + ]; @@ -16,18 +21,50 @@ - - - - + {#each GASHES as g (g.cls)} + + + + + {/each} diff --git a/packages/web/src/fx-sprites/DustPuff.svelte b/packages/web/src/fx-sprites/DustPuff.svelte index 0c5a1fc..2bd1edf 100644 --- a/packages/web/src/fx-sprites/DustPuff.svelte +++ b/packages/web/src/fx-sprites/DustPuff.svelte @@ -12,39 +12,52 @@ if (fx.side === "W") return { x: fx.cell.x * CELL, y: mid.y }; return { x: (fx.cell.x + 1) * CELL, y: mid.y }; }); + /** Edge dust blows perpendicular to the wall; loose dust just rises. */ + const away = $derived.by(() => { + if (fx.kind !== "edge-dust") return { x: 0, y: -12 }; + return fx.side === "N" || fx.side === "S" ? { x: 11, y: -4 } : { x: 4, y: -11 }; + }); + + + + + + + + + + + - + + - - - - - - - - - diff --git a/packages/web/src/fx-sprites/Hit.svelte b/packages/web/src/fx-sprites/Hit.svelte index a21b656..af14e1e 100644 --- a/packages/web/src/fx-sprites/Hit.svelte +++ b/packages/web/src/fx-sprites/Hit.svelte @@ -3,21 +3,63 @@ import { center } from "./geom"; let { fx }: { fx: FxOf<"hit"> } = $props(); const c = $derived(center(fx.at)); + const DEBRIS = [40, 165, 285]; - + + + + +{#each DEBRIS as deg, i (deg)} + +{/each} diff --git a/packages/web/src/fx-sprites/Portal.svelte b/packages/web/src/fx-sprites/Portal.svelte index 465bf7a..f9f92ca 100644 --- a/packages/web/src/fx-sprites/Portal.svelte +++ b/packages/web/src/fx-sprites/Portal.svelte @@ -9,46 +9,62 @@ {@const y1 = fx.side === "S" ? (fx.cell.y + 1) * CELL : fx.cell.y * CELL} {@const x2 = fx.side === "W" ? fx.cell.x * CELL : (fx.cell.x + 1) * CELL} {@const y2 = fx.side === "N" ? fx.cell.y * CELL : (fx.cell.y + 1) * CELL} - - + + {:else} + width={CELL - 6} height={CELL - 6} rx="6" class="veil outer" + style={`transform-origin: ${fx.at.x * CELL + CELL / 2}px ${fx.at.y * CELL + CELL / 2}px`} /> + {/if} diff --git a/packages/web/src/fx-sprites/SectorGrind.svelte b/packages/web/src/fx-sprites/SectorGrind.svelte index 296f624..3b01b78 100644 --- a/packages/web/src/fx-sprites/SectorGrind.svelte +++ b/packages/web/src/fx-sprites/SectorGrind.svelte @@ -2,48 +2,130 @@ import type { FxOf } from "../fx"; import { CELL, SECTOR } from "./geom"; let { fx }: { fx: FxOf<"sector-spin" | "sector-slide"> } = $props(); + + const S = SECTOR * CELL; + const origin = $derived(fx.kind === "sector-spin" ? fx.origin : fx.from); + const ox = $derived(origin.x * CELL); + const oy = $derived(origin.y * CELL); + const cx = $derived(ox + S / 2); + const cy = $derived(oy + S / 2); + /** Slide direction, unit-ish, for the chevron train. */ + const dir = $derived.by(() => { + if (fx.kind !== "sector-slide") return { x: 0, y: 0 }; + const dx = fx.to.x - fx.from.x, dy = fx.to.y - fx.from.y; + const m = Math.max(1, Math.hypot(dx, dy)); + return { x: dx / m, y: dy / m }; + }); + const CORNERS = $derived([ + { x: ox + 6, y: oy + 6 }, { x: ox + S - 6, y: oy + 6 }, + { x: ox + 6, y: oy + S - 6 }, { x: ox + S - 6, y: oy + S - 6 }, + ]); {#if fx.kind === "sector-spin"} - {@const ox = fx.origin.x * CELL} - {@const oy = fx.origin.y * CELL} - + + + + {#each [45, 135, 225, 315] as deg (deg)} + + {/each} + {:else} - {@const fxp = fx.from.x * CELL} - {@const fyp = fx.from.y * CELL} - + + + + {#each [0.3, 0.5, 0.7] as t, i (t)} + + {/each} + {/if} + + {#each CORNERS as p, i (i)} + + {/each} + diff --git a/packages/web/src/fx-sprites/SlimeStuck.svelte b/packages/web/src/fx-sprites/SlimeStuck.svelte index 24a78f5..429d7b1 100644 --- a/packages/web/src/fx-sprites/SlimeStuck.svelte +++ b/packages/web/src/fx-sprites/SlimeStuck.svelte @@ -1,7 +1,6 @@ - + + + + + + + + + + diff --git a/packages/web/src/fx-sprites/Streak.svelte b/packages/web/src/fx-sprites/Streak.svelte index 4291726..311cc86 100644 --- a/packages/web/src/fx-sprites/Streak.svelte +++ b/packages/web/src/fx-sprites/Streak.svelte @@ -3,42 +3,41 @@ import { center } from "./geom"; let { fx }: { fx: FxOf<"streak"> } = $props(); - const uid = $props.id(); const a = $derived(center(fx.from)); const b = $derived(center(fx.to)); + const d = $derived(`M ${a.x} ${a.y} L ${b.x} ${b.y}`); - - - - - - - - - - + + + + + diff --git a/packages/web/src/fx-sprites/TacksOw.svelte b/packages/web/src/fx-sprites/TacksOw.svelte index a75fc8d..75a4473 100644 --- a/packages/web/src/fx-sprites/TacksOw.svelte +++ b/packages/web/src/fx-sprites/TacksOw.svelte @@ -3,26 +3,41 @@ import { center } from "./geom"; let { fx }: { fx: FxOf<"tacks-ow"> } = $props(); const c = $derived(center(fx.at)); + // Each tack: head disc + spike, at its own angle and beat. + const TACKS = [ + { x: -10, y: -4, rot: -18, cls: "" }, + { x: 5, y: -9, rot: 12, cls: "late" }, + { x: -1, y: 5, rot: -5, cls: "later" }, + ]; - - - - - +{#each TACKS as t (t.cls)} + + + + + + +{/each} diff --git a/packages/web/src/fx-sprites/ThornSnap.svelte b/packages/web/src/fx-sprites/ThornSnap.svelte index 939202f..4451ad2 100644 --- a/packages/web/src/fx-sprites/ThornSnap.svelte +++ b/packages/web/src/fx-sprites/ThornSnap.svelte @@ -1,32 +1,56 @@ - + + + + + + + + + + + + + + +