import { expect, test } from "bun:test"; 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); });