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
+20 -8
View File
@@ -26,6 +26,11 @@ export interface Hit {
edge?: string;
/** The eye reached this through a warp: haze it other-worldly. */
warped?: boolean;
/** Which warp (index in board.warps) the ray first bent through, and
* how far along the ray that mouth stood — the gate a sprite must be
* beyond (its own warp) or in front of (any) to paint this column. */
warpId?: number;
warpDist?: number;
/** The first known-illusion edge the ray crossed before its solid hit:
* the eye passes, but a translucent ghost of a wall stands there. */
ghost?: { dist: number; u: number; worldU: number; axis: "x" | "y" };
@@ -108,6 +113,8 @@ export function castRay(
// a mouth re-enters at the paired mouth and marches on, its hits hazed.
let baseDist = 0;
let warped = false;
let warpId: number | undefined;
let warpDist: number | undefined;
let ghost: Hit["ghost"];
let doorway: Hit["doorway"];
for (let traversal = 0; traversal < 3; traversal++) {
@@ -171,7 +178,7 @@ export function castRay(
// 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, ghost, doorway,
axis: b.axis, worldU: along, edge: b.edge, warped, warpId, warpDist, ghost, doorway,
...(b.frame ? { frame: true } : {}),
};
}
@@ -194,10 +201,12 @@ export function castRay(
const offBoard = !view.board.cells[cellKey({ x: nx, y: ny })];
if (offBoard) {
const side: Side = crossSide;
const warp = view.board.warps.find(
const wIdx = view.board.warps.findIndex(
(w) => cellKey(w.from.cell) === cellKey({ x: cx, y: cy }) && w.from.side === side,
);
if (!warp) return { dist: baseDist + dist, kind: "rim", u: texU, axis, worldU: along, warped, ghost, doorway };
const warp = wIdx >= 0 ? view.board.warps[wIdx]! : undefined;
if (!warp) return { dist: baseDist + dist, kind: "rim", u: texU, axis, worldU: along, warped, warpId, warpDist, ghost, doorway };
if (warpId === undefined) { warpId = wIdx; warpDist = baseDist + dist; }
// Step through: re-enter at the paired mouth, heading inward, the
// lane carried by the same proper rotation the heading turns by —
// mirrored for the pairings whose axes land head-to-head.
@@ -215,14 +224,14 @@ export function castRay(
break;
}
if (view.squareContents[cellKey({ x: nx, y: ny })]?.kind === "stone") {
return { dist: baseDist + dist, kind: "stone", u: texU, axis, worldU: along, warped, ghost, doorway };
return { dist: baseDist + dist, kind: "stone", u: texU, axis, worldU: along, warped, warpId, warpDist, ghost, doorway };
}
cx = nx;
cy = ny;
}
if (!warped || traversal === 2) break;
}
return { dist: baseDist + 64, kind: "rim", u: 0, axis: "x", worldU: 0, warped, ghost, doorway };
return { dist: baseDist + 64, kind: "rim", u: 0, axis: "x", worldU: 0, warped, warpId, warpDist, ghost, doorway };
}
/** Can a wizard's body (not just their eye) cross this edge? Workshop
@@ -277,8 +286,10 @@ export interface Billboard {
bias?: number;
/** Procedural stand-in name while the painted file loads. */
fallback?: string;
/** A reflection seen through a warp mouth, not the thing itself. */
/** A reflection seen through a warp mouth, not the thing itself
* visible only through THAT warp's columns, beyond its mouth. */
warped?: boolean;
warpId?: number;
}
/** The along=0 corner of a warp mouth — the anchor castRay's lane
@@ -407,7 +418,8 @@ export function billboards(
// the same rigid motion the rays use — the zbuffer clips it to the
// opening, since everything around the mouth is nearer wall.
const real = out.slice();
for (const w of view.board.warps) {
for (let wi = 0; wi < view.board.warps.length; wi++) {
const w = view.board.warps[wi]!;
const motion = warpMotion(w);
const a2 = mouthAnchor(w.to);
const inward = SIDE_ANGLE[OPPOSITE[w.to.side]];
@@ -415,7 +427,7 @@ export function billboards(
for (const b of real) {
const rx = b.x - a2.x, ry = b.y - a2.y;
if (rx * inX + ry * inY < -0.2 || Math.hypot(rx, ry) > 8) continue;
out.push({ ...b, ...motion.toVirtual(b), warped: true });
out.push({ ...b, ...motion.toVirtual(b), warped: true, warpId: wi });
}
}
return out;