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