Files
healer-man/src/game/wowStairGeometry.test.ts
T
2026-09-13 11:07:56 -04:00

60 lines
2.9 KiB
TypeScript

import { BufferGeometry, Float32BufferAttribute } from "three";
import { describe, expect, it } from "vitest";
import { bridgeDungeonStairNavigation, smoothDungeonStairGeometry } from "./dungeonTraversalRamps";
// The offline Node 24 pipeline uses native .ts specifiers; load it at runtime,
// without making those tool-only modules part of the browser TypeScript build.
const pipelineModule = "../../scripts/dungeon-pipeline/wow-stair-geometry.ts";
const { detectDungeonStairRamps, dungeonStairNavigationBridges, dungeonStairRisers } = await import(pipelineModule);
function staircase(steps: number, rotated = false) {
const vertices: number[] = [];
for (let step = 0; step < steps; step += 1) {
const x = step;
const y = step * 0.5;
const points = [[x, y, -2], [x, y + 0.5, -2], [x, y + 0.5, 2], [x, y, -2], [x, y + 0.5, 2], [x, y, 2]];
for (const point of points) vertices.push(...(rotated ? [-point[2], point[1], point[0]] : point));
}
const geometry = new BufferGeometry();
geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
return geometry;
}
describe("WoW shipped stair detection", () => {
it.each([false, true])("detects repeated risers, including rotated stairs (%s)", (rotated) => {
const geometry = staircase(7, rotated);
const ramps = detectDungeonStairRamps(dungeonStairRisers(geometry));
expect(ramps).toHaveLength(1);
expect(ramps[0].width).toBeCloseTo(4.02);
expect(ramps[0].end[1] - ramps[0].start[1]).toBeCloseTo(3.5);
geometry.dispose();
});
it("does not interpret a single ledge or tall wall as a staircase", () => {
const ledge = staircase(1);
expect(detectDungeonStairRamps(dungeonStairRisers(ledge))).toEqual([]);
const wall = staircase(4);
const p = wall.getAttribute("position");
for (let index = 0; index < p.count; index += 1) p.setY(index, p.getY(index) * 10);
expect(detectDungeonStairRamps(dungeonStairRisers(wall))).toEqual([]);
ledge.dispose(); wall.dispose();
});
it("fills separate stair portals using existing source vertices, without bridging an unrelated floor", () => {
const geometry = new BufferGeometry();
geometry.setAttribute("position", new Float32BufferAttribute([
-1, 0, 0, 1, 0, 0, -1, 0.5, 2, 1, 0.5, 2,
-1, 0.75, 3, 1, 0.75, 3, -1, 1.5, 6, 1, 1.5, 6,
-1, 8, 3, 1, 8, 3, -1, 8, 4,
], 3));
geometry.setIndex([0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6, 8, 9, 10]);
const ramps = dungeonStairNavigationBridges(geometry, [{ id: "flight", start: [0, 0, -0.01], end: [0, 1.5, 6.01], width: 3 }]);
expect(ramps[0].navigationBridges).toHaveLength(1);
smoothDungeonStairGeometry(geometry, ramps, 0.05);
expect(() => bridgeDungeonStairNavigation(geometry, ramps, 0.05)).not.toThrow();
expect(geometry.index!.count).toBe(21);
expect(geometry.getAttribute("position").getY(8)).toBe(8);
geometry.dispose();
});
});