Files
healer-man/src/game/dungeonRecastPolicy.test.ts
T
2026-09-08 10:40:26 -04:00

175 lines
5.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
// The production Recast policy is a Node pipeline module outside the browser TypeScript project.
// @ts-expect-error There is intentionally no browser-facing declaration for this Node-only module.
import * as recastPolicy from "../../scripts/dungeon-pipeline/recast-policy.mjs";
const {
nextTiledNavMeshSize,
planNavigationBuild,
shouldRetryFailedTiledBuildWithMonotone,
shouldUseMonotonePartition,
validateTiledBuild,
} = recastPolicy;
describe("dungeon Recast build policy", () => {
it("keeps ordinary dungeon bounds on the solo builder", () => {
expect(planNavigationBuild([[0, -10, 0], [600, 20, 500]], 0.2)).toMatchObject({
mode: "solo",
gridWidth: 3000,
gridHeight: 2500,
tileCount: 1,
});
});
it("tiles continent-scale dungeon heightfields without changing voxel resolution", () => {
expect(planNavigationBuild(
[[-573.862, -141.836, -1151.037], [386.687, 889.868, 115.299]],
0.2,
)).toMatchObject({
mode: "tiled",
gridWidth: 4803,
gridHeight: 6332,
gridCells: 30_412_596,
tileSize: 512,
tileColumns: 10,
tileRows: 13,
tileCount: 130,
});
});
it("can explicitly tile compact but contour-complex source geometry", () => {
expect(planNavigationBuild([[0, 0, 0], [40, 10, 40]], 0.2, {
tileSize: 64,
maxSoloAxis: 0,
maxSoloCells: 0,
})).toMatchObject({ mode: "tiled", tileSize: 64, tileColumns: 4, tileRows: 4 });
});
it("accepts only confirmed empty-tile packing failures", () => {
const intermediates = {
buildContext: {
logs: [
{ category: 1, msg: "Building tile at x: 0, y: 0" },
{ category: 3, msg: "Failed to create Detour navmesh data" },
{ category: 1, msg: "Building tile at x: 1, y: 0" },
{ category: 3, msg: "Failed to create Detour navmesh data" },
],
},
tileIntermediates: [
{
x: 0,
y: 0,
contourSet: { nconts: () => 0 },
polyMesh: { npolys: () => 0 },
},
{
x: 1,
y: 0,
contourSet: { nconts: () => 2 },
polyMesh: { npolys: () => 4 },
},
],
};
expect(validateTiledBuild(intermediates)).toMatchObject({
valid: false,
populatedTileCount: 1,
ignoredEmptyTilePackingFailures: [{ x: 0, y: 0 }],
failures: [{ x: 1, y: 0 }],
});
});
it("rejects a tile that Detour failed to add", () => {
const intermediates = {
buildContext: {
logs: [
{ category: 1, msg: "Building tile at x: 2, y: 3" },
{ category: 2, msg: "Failed to add tile to nav mesh tx: 2, ty: 3" },
],
},
tileIntermediates: [{
x: 2,
y: 3,
contourSet: { nconts: () => 1 },
polyMesh: { npolys: () => 3 },
}],
};
expect(validateTiledBuild(intermediates)).toMatchObject({
valid: false,
failures: [{ x: 2, y: 3 }],
});
});
it("validates released intermediates against populated Detour tiles", () => {
const intermediates = {
buildContext: {
logs: [
{ category: 1, msg: "Building tile at x: 0, y: 0" },
{ category: 3, msg: "Failed to create Detour navmesh data" },
{ category: 1, msg: "Building tile at x: 1, y: 0" },
],
},
tileIntermediates: [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
],
};
expect(validateTiledBuild(intermediates, {
intermediateRetention: "released",
populatedTileCount: 1,
})).toMatchObject({
valid: true,
populatedTileCount: 1,
intermediateRetention: "released",
ignoredEmptyTilePackingFailures: [{ x: 0, y: 0, verification: "released" }],
failures: [],
});
});
it("retiles only contour-outline failures at a finer tile size", () => {
expect(nextTiledNavMeshSize({
failures: [{
message: "rcBuildContours: Bad outline for region 7, contour simplification is likely too aggressive.",
}],
}, 512)).toBe(256);
expect(nextTiledNavMeshSize({
failures: [{
message: "rcBuildRegions: 1 overlapping regions.",
}],
}, 256)).toBe(128);
expect(nextTiledNavMeshSize({
failures: [{
message: "Failed to add tile to nav mesh tx: 2, ty: 3",
}],
}, 512)).toBeNull();
expect(nextTiledNavMeshSize({
failures: [{
message: "rcBuildContours: Bad outline for region 7, contour simplification is likely too aggressive.",
}],
}, 128)).toBeNull();
});
it("uses monotone partition only when watershed overlap is the sole failure", () => {
expect(shouldUseMonotonePartition({
failures: [
{ message: "rcBuildRegions: 1 overlapping regions." },
{ message: "rcBuildRegions: 2 overlapping regions." },
],
})).toBe(true);
expect(shouldUseMonotonePartition({
failures: [
{ message: "rcBuildRegions: 1 overlapping regions." },
{ message: "rcBuildContours: Bad outline for region 7, contour simplification is likely too aggressive." },
],
})).toBe(false);
});
it("retries watershed contour triangulation failures with monotone partition only", () => {
expect(shouldRetryFailedTiledBuildWithMonotone("Failed to triangulate contours", "watershed")).toBe(true);
expect(shouldRetryFailedTiledBuildWithMonotone("Failed to triangulate contours", "monotone")).toBe(false);
expect(shouldRetryFailedTiledBuildWithMonotone("Out of memory", "watershed")).toBe(false);
});
});