Files
nage/workshop/steps/12-overlap/overlap.test.ts
T

103 lines
3.8 KiB
TypeScript

import { expect, test } from "vitest";
import { type AABB, aabbOverlap, vec } from "./given.ts";
import { penetrationVector, safeMoveAndSlide } from "./overlap.ts";
// --- penetrationVector -------------------------------------------------------
test("separated boxes -> null", () => {
const a: AABB = { x: 0, y: 0, w: 2, h: 2 };
const b: AABB = { x: 5, y: 5, w: 2, h: 2 };
expect(penetrationVector(a, b)).toBeNull();
});
test("touching edges (no strict overlap) -> null", () => {
const a: AABB = { x: 0, y: 0, w: 2, h: 2 };
const b: AABB = { x: 2, y: 0, w: 2, h: 2 };
expect(penetrationVector(a, b)).toBeNull();
});
test("shallow on x, deep on y -> pushes left (the short way out)", () => {
// a pokes 1 unit into b's left side; getting out via y would cost 6
const a: AABB = { x: 0, y: 0, w: 4, h: 4 };
const b: AABB = { x: 3, y: -2, w: 4, h: 8 };
expect(penetrationVector(a, b)).toEqual(vec(-1, 0));
});
test("mirrored -> pushes right", () => {
const a: AABB = { x: 4, y: 0, w: 4, h: 4 };
const b: AABB = { x: 1, y: -2, w: 4, h: 8 };
expect(penetrationVector(a, b)).toEqual(vec(1, 0));
});
test("shallow on y -> pushes up", () => {
const a: AABB = { x: 0, y: 0, w: 4, h: 4 };
const b: AABB = { x: -2, y: 3, w: 8, h: 4 };
expect(penetrationVector(a, b)).toEqual(vec(0, -1));
});
test("fully contained -> still gets out (all the way past the nearest edge)", () => {
// a is swallowed by b: the push must clear the whole distance to an edge,
// not just the 2-unit "overlap" of the intervals
const a: AABB = { x: 10, y: 10, w: 2, h: 2 };
const b: AABB = { x: 0, y: 0, w: 32, h: 32 };
const p = penetrationVector(a, b);
expect(p).not.toBeNull();
if (p !== null) {
const moved: AABB = { ...a, x: a.x + p.x, y: a.y + p.y };
expect(aabbOverlap(moved, b)).toBe(false);
}
});
// --- safeMoveAndSlide --------------------------------------------------------
const player: AABB = { x: 0, y: 0, w: 2, h: 2 };
test("no overlap at start -> behaves exactly like moveAndSlide", () => {
// step 10's diagonal test: x blocked at 3, y keeps going
const walls: AABB[] = [{ x: 5, y: -10, w: 2, h: 40 }];
const p = safeMoveAndSlide(player, vec(10, 10), walls);
expect(p.x).toBeCloseTo(3);
expect(p.y).toBeCloseTo(10);
});
test("starting inside a wall -> position stays a number", () => {
const inside: AABB = { x: 10, y: 10, w: 2, h: 2 };
const wall: AABB = { x: 0, y: 0, w: 32, h: 32 };
const p = safeMoveAndSlide(inside, vec(5, 0), [wall]);
expect(Number.isFinite(p.x)).toBe(true);
expect(Number.isFinite(p.y)).toBe(true);
});
test("starting inside a wall -> ends free of it", () => {
const inside: AABB = { x: 10, y: 10, w: 2, h: 2 };
const wall: AABB = { x: 0, y: 0, w: 32, h: 32 };
const p = safeMoveAndSlide(inside, vec(5, 0), [wall]);
expect(aabbOverlap({ ...inside, x: p.x, y: p.y }, wall)).toBe(false);
});
test("overlapping and walking AWAY -> actually gets away", () => {
// pokes 1 into the wall's left side; pushed out to x≈8, then walks -5
const stuck: AABB = { x: 9, y: 0, w: 2, h: 2 };
const wall: AABB = { x: 10, y: -5, w: 4, h: 10 };
const p = safeMoveAndSlide(stuck, vec(-5, 0), [wall]);
expect(p.x).toBeCloseTo(3);
expect(p.y).toBeCloseTo(0);
});
test("overlapping and pushing IN -> freed, then held at the wall", () => {
// pushed out to x≈8 first; the sweep then stops the rightward move there
const stuck: AABB = { x: 9, y: 0, w: 2, h: 2 };
const wall: AABB = { x: 10, y: -5, w: 4, h: 10 };
const p = safeMoveAndSlide(stuck, vec(5, 0), [wall]);
expect(p.x).toBeCloseTo(8);
expect(p.y).toBeCloseTo(0);
});
test("overlapping and pushing IN (rtl) -> freed, then held at the wall", () => {
const stuck: AABB = { x: 9, y: 0, w: 2, h: 2 };
const wall: AABB = { x: 10, y: -5, w: 4, h: 10 };
const p = safeMoveAndSlide(stuck, vec(-5, 0), [wall]);
expect(p.x).toBeCloseTo(3);
expect(p.y).toBeCloseTo(0);
});