Half the warp pairings were secretly mirrors
Eric walked a five-player board's corner warp and the world stretched. The lane rule — carry the along-the-edge offset through unchanged — is only correct for half the side pairings: for the rest (N to N, E to N, and their kin, the ones five-player boards deal), the proper rotation lands the entry axis head-to-head against the exit axis, and preserving the raw lane smuggled a REFLECTION into the traversal — rays crossing over at the mouth, parallax inverted, the far room swimming. warpLaneMirrored computes the pairing's sign from the rotation itself; castRay mirrors the lane where required, and warpMotion anchors the far mouth at its opposite corner to stay the same pure rotation — so sprites and projectile legs agree with the rays again. The workshop grows ?players=2..6 to stand on any table size (the mirror-class mouths live on the five-player boards). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
700db70d4e
commit
707297da17
@@ -39,6 +39,25 @@ 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" };
|
||||
|
||||
/** Unit vector along an edge's lane axis (the direction `along` grows). */
|
||||
const LANE_AXIS: Record<Side, [number, number]> = {
|
||||
E: [0, 1], W: [0, 1], N: [1, 0], S: [1, 0],
|
||||
};
|
||||
|
||||
/** Does this warp pair MIRROR the lane? A traversal is a proper rigid
|
||||
* rotation; for half the side pairings the rotated entry axis points
|
||||
* AGAINST the exit edge's axis, and preserving the raw lane there would
|
||||
* smuggle in a reflection — rays crossing over at the mouth, parallax
|
||||
* inverted, the far room stretching as the eye moves. */
|
||||
export function warpLaneMirrored(from: Side, to: Side): boolean {
|
||||
const d = SIDE_ANGLE[OPPOSITE[to]] - SIDE_ANGLE[from];
|
||||
const [x, y] = LANE_AXIS[from];
|
||||
const rx = x * Math.cos(d) - y * Math.sin(d);
|
||||
const ry = x * Math.sin(d) + y * Math.cos(d);
|
||||
const [ex2, ey] = LANE_AXIS[to];
|
||||
return rx * ex2 + ry * ey < 0;
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
@@ -180,11 +199,12 @@ export function castRay(
|
||||
);
|
||||
if (!warp) return { dist: baseDist + dist, kind: "rim", u: texU, axis, worldU: along, warped, ghost, doorway };
|
||||
// Step through: re-enter at the paired mouth, heading inward, the
|
||||
// offset along the edge preserved (a wraparound keeps its lane).
|
||||
// lane carried by the same proper rotation the heading turns by —
|
||||
// mirrored for the pairings whose axes land head-to-head.
|
||||
const exitHeading = SIDE_ANGLE[OPPOSITE[warp.to.side]];
|
||||
angle = angle + (exitHeading - SIDE_ANGLE[warp.from.side]);
|
||||
const c = warp.to.cell;
|
||||
const lane = texU;
|
||||
const lane = warpLaneMirrored(warp.from.side, warp.to.side) ? 1 - texU : texU;
|
||||
if (warp.to.side === "E") { ox = c.x + 1 - 1e-4; oy = c.y + lane; }
|
||||
else if (warp.to.side === "W") { ox = c.x + 1e-4; oy = c.y + lane; }
|
||||
else if (warp.to.side === "S") { ox = c.x + lane; oy = c.y + 1 - 1e-4; }
|
||||
@@ -280,7 +300,15 @@ export function warpMotion(w: GameView["board"]["warps"][number]): {
|
||||
} {
|
||||
const delta = SIDE_ANGLE[OPPOSITE[w.to.side]] - SIDE_ANGLE[w.from.side];
|
||||
const a1 = mouthAnchor(w.from);
|
||||
const a2 = mouthAnchor(w.to);
|
||||
// Mirrored pairings anchor the far mouth at its OTHER corner — the
|
||||
// lane runs backwards there, and this keeps the motion a pure
|
||||
// rotation matching castRay's traversal.
|
||||
const a2 = { ...mouthAnchor(w.to) };
|
||||
if (warpLaneMirrored(w.from.side, w.to.side)) {
|
||||
const [lx, ly] = LANE_AXIS[w.to.side];
|
||||
a2.x += lx;
|
||||
a2.y += ly;
|
||||
}
|
||||
const c = Math.cos(delta), s = Math.sin(delta);
|
||||
return {
|
||||
toVirtual(p) {
|
||||
|
||||
Reference in New Issue
Block a user