The maze performs: doors swing, walls die loudly, homes wear their colors

Entertainment polish for the instant replay, all through one wizard's
eyes. Doors now slide open along their own edge — Wolf3D style — when
anyone steps through in a reel, hold for the crossing, and swing shut;
the raycaster takes a per-edge openness map and doors carry their
texture with them, leaving a sliver of jamb at the far post. A wall
that dies bursts into stone and shakes the camera, then leaves a mound
of rubble at the fallen edge for the rest of the reel (a tenth
paintable sprite, drawn opaque — debris, not light). Jammed locks
seethe: a rusty pulse across the wood, keyed off the view's own door
states. Walking THROUGH a wall shimmers on both faces and washes the
walker's own screen stone-gray. And every home base wears its owner's
emblem on the flagstones — a near-white paintable overlay tinted by
wizard color per cell in the floor cast.

Testing surfaced a real ghost: a body standing near a far warp mouth
also rendered at its virtual position, which can overlap real
corridors — the depth buffer alone cannot clip it. Every column now
remembers whether its ray bent, and a sprite draws only where its warp
side matches the column's; projectile legs carry the same flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-23 16:30:35 -04:00
co-authored by Claude Fable 5
parent 8a27c06b55
commit cdfae39398
9 changed files with 238 additions and 37 deletions
+20 -2
View File
@@ -5,7 +5,7 @@
import { humanize } from "./net.svelte";
import { scheduleFx, type BoardFx } from "./fx";
import { fpFxForEvents, type FpFx } from "./fpv/fx3d";
import { castRay } from "./fpv/raycast";
import { castRay, edgeMid } from "./fpv/raycast";
import { prefs } from "./prefs.svelte";
import { stackSightTrace } from "@wizwar/engine";
import type { GameEvent, GameView } from "@wizwar/engine";
@@ -51,6 +51,24 @@
// attack in progress, so a replay-watcher can see how a spell reached them.
const sightTrace = $derived(stackSightTrace(step.view));
/** Every wall destroyed so far in the reel leaves a mound of rubble on
* the first-person floor for the rest of it. */
const rubbleSpots = $derived.by(() => {
const spots: { x: number; y: number }[] = [];
const seen = new Set<string>();
for (let i = 0; i <= Math.min(idx, steps.length - 1); i++) {
for (const e of steps[i]!.events) {
if (e.type !== "wallDestroyed" || !("edge" in e)) continue;
const edge = (e as { edge: { cell: { x: number; y: number }; side: "N" | "E" | "S" | "W" } }).edge;
const key = `${edge.cell.x},${edge.cell.y}:${edge.side}`;
if (seen.has(key)) continue;
seen.add(key);
spots.push(edgeMid(edge.cell, edge.side));
}
}
return spots;
});
/** Each step's spells flare on the reel exactly as they did at the table. */
let boardFx = $state<BoardFx[]>([]);
$effect(() => {
@@ -320,7 +338,7 @@
{#if fp}
<FirstPerson view={step.view} {povId}
x={cam.x} y={cam.y} facing={cam.facing} width={640} height={360}
fx={fpFx} posOverride={actorPos} />
fx={fpFx} posOverride={actorPos} rubble={rubbleSpots} />
{:else}
<Board view={step.view} effects={boardFx} {sightTrace} />
{/if}