Sprites respect the window they are seen through

DUKK's floor-to-ceiling white sliver, decoded: the sprite gate only
asked "did this column's ray warp?" — so a REAL body standing in front
of a warp mouth was clipped out of the mouth's columns (leaving a
baffling sliver of a huge close sprite), and a body virtualized
through warp A could paint columns bent by warp B. Every column now
remembers WHICH warp bent its ray and how far away that mouth stood;
a real body paints a column only nearer than its mouth (in front of
the window), and a virtual one only through its own warp's columns,
beyond the mouth. Projectile legs carry the same identity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-23 20:07:41 -04:00
co-authored by Claude Fable 5
parent 045e868190
commit cd9ce9fddf
3 changed files with 44 additions and 19 deletions
+17 -5
View File
@@ -238,7 +238,12 @@
// side must MATCH its column's, or bodies near a far mouth would
// ghost into real corridors the unrolled space happens to overlap.
const zbuf = new Float64Array(W);
const warpedCol = new Uint8Array(W);
// Per column: which warp (if any) the ray bent through, and how far
// away that mouth stood. A REAL body paints a column only if it is
// nearer than the mouth (it stands in front of the window); a
// VIRTUAL one only through its OWN warp's columns, beyond the mouth.
const warpIdCol = new Int32Array(W).fill(-1);
const warpDistCol = new Float64Array(W).fill(Infinity);
// 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.
@@ -250,7 +255,10 @@
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.warpId !== undefined) {
warpIdCol[col] = hit.warpId;
warpDistCol[col] = (hit.warpDist ?? 0) * Math.cos(rayAngle - facing);
}
if (hit.ghost) {
ghosts.push({
col, depth: hit.ghost.dist * Math.cos(rayAngle - facing),
@@ -341,6 +349,7 @@
scale: f.kind === "projectile" ? 0.3 : 0.25 + 0.6 * p,
rise: f.kind === "impact" ? (f.rise ?? 0.3) : 0.3,
warped: f.kind === "projectile" ? f.warped : undefined,
warpId: f.kind === "projectile" ? f.warpId : undefined,
}, ex, ey);
if (s) sprites.push({ ...s, alpha: f.kind === "impact" ? 1 - p : 1, fallback: f.art });
}
@@ -359,7 +368,9 @@
if (s.alpha !== undefined || s.glow) ctx.globalAlpha = s.alpha ?? 1;
for (let col = Math.max(0, s.left | 0); col < Math.min(W, s.right); col++) {
if (s.depth >= zbuf[col]!) continue;
if ((s.warped ? 1 : 0) !== warpedCol[col]!) continue;
if (s.warped) {
if (warpIdCol[col] !== (s.warpId ?? -2) || s.depth <= warpDistCol[col]!) continue;
} else if (s.depth >= warpDistCol[col]!) continue;
const texX = ((col - s.left) / (s.right - s.left));
if (img) {
ctx.drawImage(
@@ -437,6 +448,7 @@
top: number;
bottom: number;
warped?: boolean;
warpId?: number;
glow?: boolean;
alpha?: number;
fallback?: string;
@@ -444,7 +456,7 @@
function project(
b: { x: number; y: number; src: string; scale: number; rise: number;
aspect?: number; alpha?: number; glow?: boolean; bias?: number;
fallback?: string; warped?: boolean },
fallback?: string; warped?: boolean; warpId?: number },
ex: number, ey: number,
): Projected | null {
const relX = b.x - ex, relY = b.y - ey;
@@ -464,7 +476,7 @@
: size;
const bottom = half + wallH / 2 - b.rise * wallH;
return {
src: b.src, depth: depth - (b.bias ?? 0), warped: b.warped,
src: b.src, depth: depth - (b.bias ?? 0), warped: b.warped, warpId: b.warpId,
alpha: b.alpha, glow: b.glow, fallback: b.fallback,
left: screenX - wide / 2, right: screenX + wide / 2,
top: bottom - size, bottom,