Files

99 lines
3.5 KiB
TypeScript

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);
});