The workshop walks through warps; conjurations wear their own inks

A stride into a warp mouth now carries the walker out of the paired
mouth mid-glide, body turned exactly as the rays turn — canWalk admits
a rim edge only where a mouth waits, and the crawler splits the stride
at the crossing. And with painted conjuration sprites arriving, the fx
pass composites them normally instead of additively: painted art is
ink, not light, and additive blending would bloom its outlines white.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-23 15:28:34 -04:00
co-authored by Claude Fable 5
parent b82d526a79
commit bfece01468
3 changed files with 58 additions and 13 deletions
+37 -1
View File
@@ -7,7 +7,7 @@
import type { GameEvent, GameState, GameView } from "@wizwar/engine";
import FirstPerson from "./FirstPerson.svelte";
import Replay from "../Replay.svelte";
import { canWalk } from "./raycast";
import { canWalk, edgeMid, SIDE_ANGLE, OPPOSITE } from "./raycast";
const q = new URLSearchParams(location.search);
const seed = Number(q.get("seed") ?? 42);
@@ -44,6 +44,9 @@
type Anim =
| { kind: "glide"; fx: number; fy: number; tx: number; ty: number; t0: number; dur: number }
| { kind: "turn"; from: number; to: number; t0: number; dur: number }
| { kind: "warpglide"; fx: number; fy: number; mx: number; my: number;
nx: number; ny: number; tx: number; ty: number; dturn: number;
turned: boolean; t0: number; dur: number }
| null;
let anim: Anim = null;
const ease = (w: number) => w * w * (3 - 2 * w);
@@ -63,6 +66,27 @@
const side = SIDES4[idx]!;
const cell = { x: Math.floor(x), y: Math.floor(y) };
if (!canWalk(view, cell, side)) return;
const to = {
x: cell.x + (side === "E" ? 1 : side === "W" ? -1 : 0),
y: cell.y + (side === "S" ? 1 : side === "N" ? -1 : 0),
};
// An open rim edge canWalk allowed is a warp mouth: stride into it,
// and the stride carries on out of the paired mouth, the body turned
// exactly as the rays turn.
const warp = !view.board.cells[cellKey(to)]
? view.board.warps.find((w) => cellKey(w.from.cell) === cellKey(cell) && w.from.side === side)
: undefined;
if (warp) {
const mouth = edgeMid(cell, side);
const far = edgeMid(warp.to.cell, warp.to.side);
anim = {
kind: "warpglide", fx: x, fy: y, mx: mouth.x, my: mouth.y,
nx: far.x, ny: far.y, tx: warp.to.cell.x + 0.5, ty: warp.to.cell.y + 0.5,
dturn: SIDE_ANGLE[OPPOSITE[warp.to.side]] - SIDE_ANGLE[warp.from.side],
turned: false, t0: now, dur: 320,
};
return;
}
const tx = x + (side === "E" ? 1 : side === "W" ? -1 : 0);
const ty = y + (side === "S" ? 1 : side === "N" ? -1 : 0);
anim = { kind: "glide", fx: x, fy: y, tx, ty, t0: now, dur: 280 };
@@ -76,6 +100,18 @@
if (anim.kind === "glide") {
x = anim.fx + (anim.tx - anim.fx) * ease(w);
y = anim.fy + (anim.ty - anim.fy) * ease(w);
} else if (anim.kind === "warpglide") {
// Half the stride reaches the mouth; the moment it crosses, the
// eye stands at the far mouth, turned, finishing the stride.
const e = ease(w);
if (e < 0.5) {
x = anim.fx + (anim.mx - anim.fx) * (e * 2);
y = anim.fy + (anim.my - anim.fy) * (e * 2);
} else {
if (!anim.turned) { facing += anim.dturn; anim.turned = true; }
x = anim.nx + (anim.tx - anim.nx) * ((e - 0.5) * 2);
y = anim.ny + (anim.ty - anim.ny) * ((e - 0.5) * 2);
}
} else {
facing = anim.from + (anim.to - anim.from) * ease(w);
}