forked from phenom/healer-man
175 lines
5.5 KiB
TypeScript
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);
|
|
});
|
|
});
|