Walls you know are lies stand as ghosts
A wizard who has seen through an illusion no longer walks toward empty air where the board shows a dashed ghost: the ray records the first known-illusion edge it crosses without stopping, and the renderer stands a wall there at one-fifth opacity, violet ripple running through it, drawn over the sprites so bodies show through its breath. Untested illusions get the same tell the board gives — a faint shimmer on their solid face, maybe stone, maybe not — while a believed illusion stays honest brick. The workshop rehearses it with ?illusion=H:2,8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b3bc4d079b
commit
727b9144d0
@@ -219,12 +219,22 @@
|
||||
// ghost into real corridors the unrolled space happens to overlap.
|
||||
const zbuf = new Float64Array(W);
|
||||
const warpedCol = new Uint8Array(W);
|
||||
// Known illusions: the ray passes, but a translucent ghost of a wall
|
||||
// stands at the crossing — drawn after the sprites so bodies show
|
||||
// through it, shimmering so nobody mistakes it for stone.
|
||||
const ghosts: { col: number; depth: number; u: number; worldU: number }[] = [];
|
||||
for (let col = 0; col < W; col++) {
|
||||
const rayAngle = facing + Math.atan((col / W - 0.5) * 2 * Math.tan(FOV / 2));
|
||||
const hit = castRay(view, ex, ey, rayAngle, doors);
|
||||
const depth = hit.dist * Math.cos(rayAngle - facing); // no fisheye
|
||||
zbuf[col] = depth;
|
||||
warpedCol[col] = hit.warped ? 1 : 0;
|
||||
if (hit.ghost) {
|
||||
ghosts.push({
|
||||
col, depth: hit.ghost.dist * Math.cos(rayAngle - facing),
|
||||
u: hit.ghost.u, worldU: hit.ghost.worldU,
|
||||
});
|
||||
}
|
||||
const wallH = Math.min(H * 2.5, H / Math.max(depth, 0.05));
|
||||
const top = half - wallH / 2;
|
||||
const tex = textures[hit.kind] ?? textures.wall!;
|
||||
@@ -250,6 +260,13 @@
|
||||
ctx.fillStyle = `rgba(190,150,255,${Math.max(0, swirl)})`;
|
||||
ctx.fillRect(col, top, 1, wallH);
|
||||
}
|
||||
if (hit.kind === "wall" && hit.edge && view.illusionEdges[hit.edge] === "untested") {
|
||||
// The same tell the board gives: an untested illusion's face
|
||||
// shimmers faintly — maybe stone, maybe not.
|
||||
const tell = 0.06 + 0.05 * Math.sin(time / 260 + hit.worldU * 13);
|
||||
ctx.fillStyle = `rgba(190,160,255,${Math.max(0, tell)})`;
|
||||
ctx.fillRect(col, top, 1, wallH);
|
||||
}
|
||||
if (hit.kind === "door" && hit.edge && view.doorStates[hit.edge] === "jammed") {
|
||||
// A jammed lock seethes: a rusty seal pulsing across the wood.
|
||||
const seethe = 0.10 + 0.08 * Math.sin(time / 160 + hit.worldU * 22);
|
||||
@@ -322,6 +339,21 @@
|
||||
if (s.glow) ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// The ghosts of walls you know are lies: drawn over everything at
|
||||
// their columns, thin as breath, rippling.
|
||||
for (const gh of ghosts) {
|
||||
const gHft = Math.min(H * 2.5, H / Math.max(gh.depth, 0.05));
|
||||
const gTop = half - gHft / 2;
|
||||
const tex = textures.wall!;
|
||||
ctx.globalAlpha = 0.2;
|
||||
ctx.drawImage(tex, Math.min(tex.width - 1, gh.u * tex.width), 0,
|
||||
Math.max(1, tex.width / 96), tex.height, gh.col, gTop, 1, gHft);
|
||||
ctx.globalAlpha = 1;
|
||||
const ripple = 0.10 + 0.07 * Math.sin(time / 200 + gh.worldU * 11 + gHft * 0.01);
|
||||
ctx.fillStyle = `rgba(190,160,255,${Math.max(0, ripple)})`;
|
||||
ctx.fillRect(gh.col, gTop, 1, gHft);
|
||||
}
|
||||
|
||||
// A whisper of vignette holds the torchlit mood together.
|
||||
const vig = ctx.createRadialGradient(W / 2, half, H * 0.35, W / 2, half, H * 0.95);
|
||||
vig.addColorStop(0, "rgba(0,0,0,0)");
|
||||
|
||||
Reference in New Issue
Block a user