import { expect, test } from "vitest"; import { resolveOverlaps, safeMoveAndSlide } from "./crush.ts"; import { type AABB, aabbOverlap, vec } from "./given.ts"; // --- resolveOverlaps --------------------------------------------------------- test("free box -> untouched, settled", () => { const r = resolveOverlaps({ x: 0, y: 0, w: 2, h: 2 }, [ { x: 5, y: 5, w: 2, h: 2 }, ]); expect(r.settled).toBe(true); expect(r.x).toBe(0); expect(r.y).toBe(0); }); test("single overlap -> pushed out, settled", () => { const r = resolveOverlaps({ x: 9, y: 0, w: 2, h: 2 }, [ { x: 10, y: -5, w: 4, h: 10 }, ]); expect(r.settled).toBe(true); expect(r.x).toBeCloseTo(8); // the push was x-only: the axis that needed no fixing must come back // EXACTLY untouched — not "close to" untouched expect(r.y).toBe(0); }); // pushed out of A (left) -> lands inside B -> a second push (downward, B is // shallow) frees it. One pass can't see this coming; the negotiation can. const chainA: AABB = { x: 10, y: 0, w: 4, h: 4 }; const chainB: AABB = { x: 4, y: 0, w: 4.5, h: 1.4 }; test("a push that lands you in the NEXT wall still resolves", () => { const hero: AABB = { x: 9, y: 1, w: 2, h: 2 }; const r = resolveOverlaps(hero, [chainA, chainB]); expect(r.settled).toBe(true); const moved = { ...hero, x: r.x, y: r.y }; expect(aabbOverlap(moved, chainA)).toBe(false); expect(aabbOverlap(moved, chainB)).toBe(false); }); test("...and the answer must not depend on wall order", () => { const hero: AABB = { x: 9, y: 1, w: 2, h: 2 }; const r = resolveOverlaps(hero, [chainB, chainA]); expect(r.settled).toBe(true); const moved = { ...hero, x: r.x, y: r.y }; expect(aabbOverlap(moved, chainA)).toBe(false); expect(aabbOverlap(moved, chainB)).toBe(false); }); // the gap between these two walls is 1.5 wide; the hero is 2 wide. // there is NO overlap-free position — the pushes ping-pong forever. const vice: AABB[] = [ { x: 10, y: -5, w: 4, h: 10 }, { x: 6.5, y: -5, w: 2, h: 10 }, ]; test("gap narrower than the box -> reports the crush, stays finite", () => { const r = resolveOverlaps({ x: 9, y: 0, w: 2, h: 2 }, vice); expect(r.settled).toBe(false); expect(Number.isFinite(r.x)).toBe(true); expect(Number.isFinite(r.y)).toBe(true); }); // --- safeMoveAndSlide -------------------------------------------------------- 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({ x: 0, y: 0, w: 2, h: 2 }, vec(10, 10), walls); expect(p.x).toBeCloseTo(3); expect(p.y).toBeCloseTo(10); }); test("resolvable overlap, then walking away -> freed and gone", () => { const p = safeMoveAndSlide({ x: 9, y: 0, w: 2, h: 2 }, vec(-5, 0), [ { x: 10, y: -5, w: 4, h: 10 }, ]); expect(p.x).toBeCloseTo(3); expect(p.y).toBeCloseTo(0); }); test("crushed with zero velocity -> a number, not a ghost", () => { // the capstone bug: ledge pushes hero into the pillar, hero holds still const p = safeMoveAndSlide({ x: 9, y: 0, w: 2, h: 2 }, vec(0, 0), vice); expect(Number.isFinite(p.x)).toBe(true); expect(Number.isFinite(p.y)).toBe(true); // still pinched between the walls — not flung across the room expect(p.x).toBeGreaterThan(7); expect(p.x).toBeLessThan(9.5); }); test("crushed and still pushing -> the walls win", () => { const p = safeMoveAndSlide({ x: 9, y: 0, w: 2, h: 2 }, vec(5, 0), vice); expect(Number.isFinite(p.x)).toBe(true); expect(Number.isFinite(p.y)).toBe(true); expect(p.x).toBeGreaterThan(7); expect(p.x).toBeLessThan(9.5); });