Files
healer-man/scripts/dungeon-pipeline/detour-component-selection.test.mjs
T
2026-10-07 13:05:04 -04:00

89 lines
3.9 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
countSurfaceBucketReferences,
selectDetourAnchorComponents,
} from "./detour-component-selection.mjs";
function topologyFixture() {
const polygons = [
[0, 0, 0, 0, 0, 0, 0, 2, 4],
[1, 0, 0, 0, 0, 1, 2, 1, 3],
[2, 1, 1, 0, 0, 0, 3, 3, 4],
[3, 2, 2, 0, 0, 0, 6, 1, 3],
];
const link = (from, to, edge) => [from, to, edge, 0, 0, 255, 0, 1, from, 0, 0, to, 0, 0];
return {
schemaVersion: 1,
polygonCount: 4,
tileCount: 3,
detailTriangleCount: 7,
geometryTriangleCount: 7,
polygonFanTriangleCount: 5,
directedLinkCount: 2,
unrepresentedLinkCount: 0,
offMeshPolygonCount: 0,
weakComponents: { count: 3, largestPolygonCount: 2, polygonCounts: [2, 1, 1] },
polygons,
links: [link(0, 2, 0), link(2, 0, 1)],
};
}
test("source anchors retain complete weak Detour components and remap only exact source links", () => {
const selected = selectDetourAnchorComponents(topologyFixture(), [
{ id: "entry", kind: "entry", position: [0, 0, 0], polygonId: 0, distance: 0.2, maximumDistance: 0.5, required: true },
{ id: "boss", kind: "boss", position: [100, 0, 0], polygonId: 3, distance: 0.1, maximumDistance: 0.25, required: true },
{ id: "far-combat", kind: "combat", position: [50, 0, 0], polygonId: 1, distance: 7, maximumDistance: 4 },
]);
assert.deepEqual([...selected.selectedSourcePolygonIds], [0, 2, 3]);
assert.deepEqual([...selected.sourceToSelectedPolygonId], [[0, 0], [2, 1], [3, 2]]);
assert.equal(selected.topology.polygonCount, 3);
assert.equal(selected.topology.directedLinkCount, 2);
assert.equal(selected.topology.detailTriangleCount, 6);
assert.deepEqual(selected.topology.polygons.map((row) => row.slice(0, 8)), [
[0, 0, 0, 0, 0, 0, 0, 2],
[1, 1, 1, 0, 0, 0, 2, 3],
[2, 2, 2, 0, 0, 0, 5, 1],
]);
assert.deepEqual(selected.topology.links.map((row) => row.slice(0, 2)), [[0, 1], [1, 0]]);
assert.equal(selected.summary.retainedAnchorIds.includes("entry"), true);
assert.equal(selected.summary.retainedAnchorIds.includes("boss"), true);
assert.equal(selected.summary.unmatchedAnchors[0].id, "far-combat");
assert.equal(selected.summary.excluded.polygonCount, 1);
assert.equal(selected.summary.excluded.disposition, "unanchored-components-excluded; not individually proven decorative");
});
test("selection fails closed when an anchored component exceeds any runtime budget", () => {
const source = topologyFixture();
assert.throws(() => selectDetourAnchorComponents(source, [
{ id: "entry", polygonId: 0, distance: 0, maximumDistance: 1 },
], { limits: { polygons: 1 } }), /polygons budget/);
assert.throws(() => selectDetourAnchorComponents(source, [
{ id: "entry", polygonId: 0, distance: 0, maximumDistance: 1 },
], { limits: { directedLinks: 1 } }), /directedLinks budget/);
assert.throws(() => selectDetourAnchorComponents(source, [
{ id: "entry", polygonId: 0, distance: 0, maximumDistance: 1 },
], { limits: { detailTriangles: 1 } }), /detailTriangles budget/);
assert.throws(() => selectDetourAnchorComponents(source, [
{ id: "entry", polygonId: 0, distance: 0, maximumDistance: 1 },
], { limits: { surfaceBucketReferences: 3 }, surfaceBucketReferenceCount: 4 }), /surfaceBucketReferences budget/);
});
test("source anchors outside their explicit projection limit do not select components", () => {
assert.throws(() => selectDetourAnchorComponents(topologyFixture(), [
{ id: "unprojected", polygonId: 2, distance: 4.001, maximumDistance: 4 },
]), /No source anchors map/);
});
test("surface bucket references match runtime's XZ cell coverage rule", () => {
const positions = new Float32Array([
0, 0, 0,
8, 0, 0,
0, 0, 8,
-0.1, 0, -0.1,
]);
const indices = new Uint32Array([0, 1, 2, 0, 2, 3]);
assert.equal(countSurfaceBucketReferences(positions, indices, 8), 10);
});