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
+17 -4
View File
@@ -25,8 +25,8 @@ export interface Hit {
warped?: boolean;
}
const SIDE_ANGLE: Record<Side, number> = { E: 0, S: Math.PI / 2, W: Math.PI, N: -Math.PI / 2 };
const OPPOSITE: Record<Side, Side> = { E: "W", W: "E", N: "S", S: "N" };
export const SIDE_ANGLE: Record<Side, number> = { E: 0, S: Math.PI / 2, W: Math.PI, N: -Math.PI / 2 };
export const OPPOSITE: Record<Side, Side> = { E: "W", W: "E", N: "S", S: "N" };
/** Is this edge passable to the EYE (rays), and if not, what is it? */
function edgeObstacle(view: GameView, key: string): Hit["kind"] | null {
@@ -155,7 +155,8 @@ export function castRay(view: GameView, ox: number, oy: number, angle: number):
}
/** Can a wizard's body (not just their eye) cross this edge? Workshop
* collision: walls and closed doors stop you, fire and warps do not. */
* collision: walls and closed doors stop you; fire does not, and an open
* rim edge carries you only where a warp mouth waits. */
export function canWalk(view: GameView, from: Cell, side: Side): boolean {
const key = edgeKey(from, side);
const e = view.board.edges[key] ?? "open";
@@ -166,11 +167,23 @@ export function canWalk(view: GameView, from: Cell, side: Side): boolean {
x: from.x + (side === "E" ? 1 : side === "W" ? -1 : 0),
y: from.y + (side === "S" ? 1 : side === "N" ? -1 : 0),
};
if (!view.board.cells[cellKey(to)]) return false; // the rim (warp-walk later)
if (!view.board.cells[cellKey(to)]) {
return view.board.warps.some(
(w) => cellKey(w.from.cell) === cellKey(from) && w.from.side === side,
);
}
if (view.squareContents[cellKey(to)]?.kind === "stone") return false;
return true;
}
/** The midpoint of one cell edge, in world units. */
export function edgeMid(cell: Cell, side: Side): { x: number; y: number } {
if (side === "E") return { x: cell.x + 1, y: cell.y + 0.5 };
if (side === "W") return { x: cell.x, y: cell.y + 0.5 };
if (side === "S") return { x: cell.x + 0.5, y: cell.y + 1 };
return { x: cell.x + 0.5, y: cell.y };
}
export interface Billboard {
/** World position (cell-centered). */
x: number;