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:
co-authored by
Claude Fable 5
parent
8a27c06b55
commit
cdfae39398
@@ -21,6 +21,8 @@ export interface Hit {
|
||||
/** The un-wrapped along-face coordinate: u plus the world cell it lies
|
||||
* in, so multi-cell surfaces (a long wall of fire) can read as one. */
|
||||
worldU: number;
|
||||
/** The edge struck, for state the renderer dresses (jammed locks). */
|
||||
edge?: string;
|
||||
/** The eye reached this through a warp: haze it other-worldly. */
|
||||
warped?: boolean;
|
||||
}
|
||||
@@ -28,15 +30,13 @@ export interface Hit {
|
||||
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? */
|
||||
/** Is this edge passable to the EYE (rays), and if not, what is it?
|
||||
* Doors always report as doors; how far each stands open — fully for an
|
||||
* open or held door, mid-swing during an animation — is castRay's call. */
|
||||
function edgeObstacle(view: GameView, key: string): Hit["kind"] | null {
|
||||
const e = view.board.edges[key] ?? "open";
|
||||
if (e === "open") return null;
|
||||
if (e === "door") {
|
||||
// An open or held door is a doorway; the eye passes through the gap.
|
||||
if (view.openDoorEdges.includes(key) || view.heldDoorEdges.includes(key)) return null;
|
||||
return "door";
|
||||
}
|
||||
if (e === "door") return "door";
|
||||
if (e === "firewall") return "firewall";
|
||||
return "wall";
|
||||
}
|
||||
@@ -69,7 +69,13 @@ function slabHit(
|
||||
return { t: Math.max(tNear, 0), axis: tx1 > ty1 ? "x" : "y" };
|
||||
}
|
||||
|
||||
export function castRay(view: GameView, ox: number, oy: number, angle: number): Hit {
|
||||
export function castRay(
|
||||
view: GameView, ox: number, oy: number, angle: number,
|
||||
/** Transient door openness (0 shut - 1 wide), keyed by edge; doors the
|
||||
* view holds open stand at 1 without an entry. Wolf3D-style: an open
|
||||
* door has slid along its own edge, the gap growing from u=0. */
|
||||
doors?: Record<string, number>,
|
||||
): Hit {
|
||||
// Sight runs THROUGH warps as the rules say it does: a ray that reaches
|
||||
// a mouth re-enters at the paired mouth and marches on, its hits hazed.
|
||||
let baseDist = 0;
|
||||
@@ -90,12 +96,25 @@ export function castRay(view: GameView, ox: number, oy: number, angle: number):
|
||||
// Every blocked edge of the current cell stands as a slab; the
|
||||
// nearest strike inside this cell's span wins.
|
||||
const exit = Math.min(sideDistX, sideDistY);
|
||||
let best: { t: number; axis: "x" | "y"; kind: Hit["kind"] } | null = null;
|
||||
let best: { t: number; axis: "x" | "y"; kind: Hit["kind"]; slide: number; edge: string } | null = null;
|
||||
const trySide = (side: Side, minX: number, maxX: number, minY: number, maxY: number) => {
|
||||
const kind = edgeObstacle(view, edgeKey({ x: cx, y: cy }, side));
|
||||
const key = edgeKey({ x: cx, y: cy }, side);
|
||||
const kind = edgeObstacle(view, key);
|
||||
if (!kind) return;
|
||||
const h = slabHit(ox, oy, dx, dy, minX, maxX, minY, maxY);
|
||||
if (h && h.t <= exit + 0.15 && (!best || h.t < best.t)) best = { ...h, kind };
|
||||
if (!h || h.t > exit + 0.15 || (best && h.t >= best.t)) return;
|
||||
let slide = 0;
|
||||
if (kind === "door") {
|
||||
const open = doors?.[key] ??
|
||||
(view.openDoorEdges.includes(key) || view.heldDoorEdges.includes(key) ? 1 : 0);
|
||||
if (open > 0) {
|
||||
const along = h.axis === "x" ? oy + h.t * dy : ox + h.t * dx;
|
||||
const u = along - Math.floor(along);
|
||||
if (u < open - 0.02) return; // the ray sails through the opening
|
||||
slide = open;
|
||||
}
|
||||
}
|
||||
best = { ...h, kind, slide, edge: key };
|
||||
};
|
||||
const hw = (side: Side) =>
|
||||
HALF_THICK[edgeObstacle(view, edgeKey({ x: cx, y: cy }, side)) ?? "wall"] ?? 0.07;
|
||||
@@ -104,10 +123,14 @@ export function castRay(view: GameView, ox: number, oy: number, angle: number):
|
||||
trySide("S", cx, cx + 1, cy + 1 - hw("S"), cy + 1 + hw("S"));
|
||||
trySide("N", cx, cx + 1, cy - hw("N"), cy + hw("N"));
|
||||
if (best !== null) {
|
||||
const b = best as { t: number; axis: "x" | "y"; kind: Hit["kind"] };
|
||||
const b = best as { t: number; axis: "x" | "y"; kind: Hit["kind"]; slide: number; edge: string };
|
||||
const along = b.axis === "x" ? oy + b.t * dy : ox + b.t * dx;
|
||||
const u = along - Math.floor(along);
|
||||
return { dist: baseDist + b.t, kind: b.kind, u, axis: b.axis, worldU: along, warped };
|
||||
// A sliding door carries its texture with it: sample past the gap.
|
||||
return {
|
||||
dist: baseDist + b.t, kind: b.kind, u: Math.max(0, u - b.slide),
|
||||
axis: b.axis, worldU: along, edge: b.edge, warped,
|
||||
};
|
||||
}
|
||||
|
||||
// No slab in this cell: advance through the open boundary.
|
||||
|
||||
Reference in New Issue
Block a user