test(02-02): add real 13x13 game maps to pathfinding performance tests

- 夹具 createPerformanceSystem 参数化起始位置与图块定义(合成矩阵默认值不变)
- 真实地图图块语义:数字 1 墙体,0/2/3/4/5/6 均为空地
- 新增六张 13x13 真实地图分段测量用例(20 轮 build/find/search,入口不足时固定种子抽选端点并记录)
This commit is contained in:
unanmed 2026-09-09 20:28:21 +08:00
parent f9c4f3b44a
commit f1a9e626b6

View File

@ -68,6 +68,12 @@ const MAP_SIZES = [10, 30, 60, 100, 150];
/** 分段测量重复运行的次数 */
const RUNS = 5;
/** 真实地图分段测量重复运行的次数13x13 图极小需更多样本稳定均值 */
const REAL_RUNS = 20;
/** 真实地图端点伪随机抽选的固定种子 */
const REALMAP_SEED = 20260909;
interface MeasureResult<T> {
/** 被测函数的返回值 */
value: T;
@ -218,18 +224,49 @@ function generateRows(size: number): number[] {
return rows;
}
interface TestTileDef {
/** 图块数字 */
num: number;
/** 图块 id 别名 */
id: string;
/** 离开该图块的通行掩码 */
outPass: number;
/** 进入该图块的通行掩码 */
inPass: number;
}
/** 合成尺寸矩阵夹具的图块定义:数字 1 为开阔空地,数字 6 为墙体 */
const SYNTHETIC_TILE_DEFS: TestTileDef[] = [
{ num: 1, id: 'open', outPass: 15, inPass: 15 },
{ num: 6, id: 'wall', outPass: 0, inPass: 0 }
];
/** 真实地图夹具的图块定义:数字 1 为墙体其余数字0/2/3/4/5/6均视为开阔空地 */
const REALMAP_TILE_DEFS: TestTileDef[] = [
{ num: 1, id: 'wall', outPass: 0, inPass: 0 },
{ num: 0, id: 'open0', outPass: 15, inPass: 15 },
{ num: 2, id: 'open2', outPass: 15, inPass: 15 },
{ num: 3, id: 'open3', outPass: 15, inPass: 15 },
{ num: 4, id: 'open4', outPass: 15, inPass: 15 },
{ num: 5, id: 'open5', outPass: 15, inPass: 15 },
{ num: 6, id: 'open6', outPass: 15, inPass: 15 }
];
/**
*
* @param rows
* @param width
* @param start
* @param defs /
*/
function createPerformanceSystem(rows: number[], width: number) {
function createPerformanceSystem(
rows: number[],
width: number,
start: ITileLocator = { x: 0, y: 0 },
defs: TestTileDef[] = SYNTHETIC_TILE_DEFS
) {
const tileStore: ITileStore = new modules.TileStore() as never;
const tileDefs = [
{ num: 1, id: 'open', outPass: 15, inPass: 15 },
{ num: 6, id: 'wall', outPass: 0, inPass: 0 }
];
for (const tile of tileDefs) {
for (const tile of defs) {
tileStore.addTile({
num: tile.num,
id: tile.id,
@ -312,6 +349,7 @@ function createPerformanceSystem(rows: number[], width: number) {
}
const tile = new PerfTile();
tile.setPos(start.x, start.y);
const system: PathfindingSystem = new modules.PathfindingSystem(
commonState as never
);
@ -326,11 +364,172 @@ function createPerformanceSystem(rows: number[], width: number) {
builder.useMapState(maps);
builder.useMapLayer(layer);
builder.usePassPredicate(predicate);
return builder.build({ x: 0, y: 0 });
return builder.build(start);
}
};
}
interface IRealMapEndpoints {
/** 寻路起始位置 */
start: ITileLocator;
/** 寻路目标位置 */
target: ITileLocator;
/** 是否存在伪随机抽选的端点,需在输出中记录所选图块 */
seeded: boolean;
}
/**
* 5
*
* @param map
* @param size
*/
function resolveEndpoints(map: number[][], size: number): IRealMapEndpoints {
const entrances: ITileLocator[] = [];
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if (map[y]![x] === 5) entrances.push({ x, y });
}
}
if (entrances.length >= 2) {
return {
start: entrances[0]!,
target: entrances[1]!,
seeded: false
};
}
let seed = REALMAP_SEED;
const next = () => {
seed = (seed * 1664525 + 1013904223) % 4294967296;
return seed / 4294967296;
};
const pick = (): ITileLocator => {
while (true) {
const index = Math.floor(next() * size * size);
const x = index % size;
const y = Math.floor(index / size);
if (map[y]![x] !== 1) return { x, y };
}
};
const start = entrances[0] ?? pick();
let target = pick();
while (target.x === start.x && target.y === start.y) {
target = pick();
}
return { start, target, seeded: true };
}
/**
* min/avg ms
* @param times ms
*/
function minAvg(times: number[]): string {
const min = Math.min(...times);
const avg = times.reduce((a, b) => a + b, 0) / times.length;
return `${min.toFixed(2)}/${avg.toFixed(2)}ms`;
}
/** 用户提供的六张 13x13 真实游戏地图1 为墙体5 为入口,其余数字均为空地 */
const REAL_MAPS: number[][][] = [
// 地图 1无入口需伪随机抽取空地端点
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 4, 2, 0, 3, 1, 5, 2, 0, 3, 3, 3, 1],
[1, 3, 1, 3, 0, 2, 4, 1, 4, 1, 1, 2, 1],
[1, 1, 1, 1, 4, 1, 1, 1, 3, 0, 4, 0, 1],
[1, 0, 4, 3, 0, 4, 3, 0, 4, 1, 1, 3, 1],
[1, 4, 1, 1, 1, 1, 4, 1, 0, 3, 1, 1, 1],
[1, 0, 3, 1, 3, 1, 3, 1, 1, 4, 1, 3, 1],
[1, 3, 0, 4, 0, 4, 0, 3, 1, 0, 2, 0, 1],
[1, 1, 4, 1, 1, 1, 2, 1, 1, 2, 1, 4, 1],
[1, 3, 0, 4, 3, 4, 0, 0, 4, 0, 4, 0, 1],
[1, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 4, 1],
[1, 3, 1, 3, 3, 3, 0, 0, 3, 3, 1, 3, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
// 地图 2入口 (12,11) 与 (1,12)
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1],
[1, 0, 4, 0, 2, 0, 2, 0, 2, 0, 4, 0, 1],
[1, 1, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1],
[1, 3, 0, 0, 2, 0, 2, 0, 4, 0, 0, 3, 1],
[1, 0, 3, 0, 1, 4, 1, 4, 1, 1, 2, 1, 1],
[1, 3, 0, 4, 1, 0, 4, 0, 0, 4, 0, 3, 1],
[1, 1, 1, 0, 1, 3, 0, 1, 4, 1, 1, 1, 1],
[1, 3, 1, 4, 1, 1, 0, 2, 0, 3, 1, 3, 1],
[1, 0, 4, 0, 4, 0, 4, 1, 3, 0, 4, 0, 1],
[1, 1, 0, 1, 0, 1, 0, 4, 0, 0, 1, 1, 1],
[1, 3, 0, 1, 3, 1, 3, 0, 1, 4, 0, 3, 5],
[1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
// 地图 3入口 (10,6) 与 (2,10)
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 0, 3, 1],
[1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 3, 3, 1],
[1, 3, 0, 3, 4, 3, 4, 0, 3, 4, 3, 3, 1],
[1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 3, 1, 0, 0, 4, 3, 1, 3, 0, 3, 1],
[1, 1, 4, 1, 4, 1, 3, 1, 1, 0, 5, 0, 1],
[1, 3, 3, 1, 3, 4, 0, 3, 2, 4, 0, 4, 1],
[1, 2, 1, 1, 2, 1, 1, 4, 1, 1, 1, 2, 1],
[1, 3, 0, 4, 3, 1, 1, 3, 1, 3, 1, 3, 1],
[1, 0, 5, 1, 0, 1, 1, 0, 4, 0, 1, 0, 1],
[1, 3, 1, 1, 3, 4, 3, 0, 1, 4, 2, 3, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
// 地图 4入口 (6,1) 与 (6,11)
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 3, 2, 4, 0, 5, 0, 0, 4, 0, 3, 1],
[1, 3, 1, 1, 2, 1, 0, 1, 2, 1, 3, 0, 1],
[1, 0, 3, 1, 3, 4, 0, 4, 3, 1, 2, 1, 1],
[1, 1, 4, 1, 2, 1, 3, 1, 2, 1, 0, 0, 1],
[1, 0, 3, 4, 0, 0, 2, 0, 3, 1, 3, 3, 1],
[1, 3, 0, 2, 0, 3, 1, 3, 0, 4, 0, 0, 1],
[1, 0, 1, 0, 4, 1, 1, 1, 4, 1, 1, 1, 1],
[1, 4, 1, 4, 3, 0, 1, 0, 3, 1, 0, 3, 1],
[1, 0, 1, 3, 1, 3, 2, 3, 0, 1, 3, 0, 1],
[1, 0, 1, 0, 1, 4, 0, 4, 1, 1, 1, 4, 1],
[1, 3, 1, 3, 1, 3, 5, 3, 4, 0, 3, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
// 地图 5入口 (11,1) 与 (0,7)
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 3, 0, 0, 4, 0, 1, 5, 1],
[1, 1, 3, 3, 1, 0, 3, 1, 1, 3, 1, 0, 1],
[1, 1, 3, 1, 1, 1, 2, 1, 0, 0, 4, 0, 1],
[1, 1, 2, 1, 1, 3, 4, 1, 2, 1, 1, 1, 1],
[1, 1, 4, 3, 1, 0, 3, 2, 0, 1, 0, 3, 1],
[1, 1, 0, 1, 1, 1, 1, 1, 3, 2, 4, 3, 1],
[5, 0, 4, 0, 3, 0, 0, 4, 0, 1, 0, 0, 1],
[1, 4, 1, 4, 1, 1, 1, 1, 4, 1, 2, 1, 1],
[1, 3, 1, 3, 1, 3, 0, 1, 0, 1, 2, 1, 1],
[1, 3, 1, 0, 2, 0, 3, 4, 0, 4, 0, 3, 1],
[1, 3, 2, 0, 1, 0, 0, 1, 3, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
// 地图 6入口 (0,1) 与 (6,12),注意 (10,9) 的 6 按语义为空地
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[5, 2, 0, 3, 0, 4, 0, 4, 3, 3, 3, 0, 1],
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 4, 1],
[1, 3, 3, 0, 4, 1, 0, 1, 3, 3, 4, 0, 1],
[1, 0, 1, 1, 2, 1, 4, 1, 1, 1, 1, 4, 1],
[1, 4, 1, 3, 3, 1, 0, 1, 4, 0, 3, 0, 1],
[1, 2, 1, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 2, 4, 2, 0, 0, 0, 0, 1],
[1, 1, 2, 1, 1, 1, 2, 1, 1, 4, 0, 4, 1],
[1, 3, 4, 3, 1, 3, 0, 3, 1, 1, 6, 1, 1],
[1, 1, 2, 1, 1, 0, 1, 0, 1, 0, 3, 0, 1],
[1, 3, 4, 3, 1, 3, 0, 3, 1, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1]
]
];
describe('pathfinding performance', () => {
// 验证各尺寸障碍地图上从建图到最优路径发现的完整寻路耗时均处于可用量级
it('completes the full find pipeline across the map size matrix within the sanity bounds', () => {
@ -395,4 +594,65 @@ describe('pathfinding performance', () => {
expect(steps).toBeGreaterThan(0);
}
});
// 验证用户提供的六张 13x13 真实游戏地图上入口到入口的分段寻路耗时与可达性
it('measures graph build and search phases on the six real 13x13 game maps', () => {
for (let i = 0; i < REAL_MAPS.length; i++) {
const map = REAL_MAPS[i]!;
const endpoints = resolveEndpoints(map, 13);
const fixture = createPerformanceSystem(
map.flat(),
13,
endpoints.start,
REALMAP_TILE_DEFS
);
// 伪随机抽选的端点需单独记录所选图块,保证运行结果可复现审查
if (endpoints.seeded) {
// eslint-disable-next-line no-console
console.log(
`[pathfinding-realmap] map=${i + 1} seed-picked ` +
`start=(${endpoints.start.x},${endpoints.start.y}) ` +
`target=(${endpoints.target.x},${endpoints.target.y})`
);
}
const buildTimes: number[] = [];
const findTimes: number[] = [];
let steps = 0;
for (let run = 0; run < REAL_RUNS; run++) {
const build = measureCall('pathfinding-realmap-build', () =>
fixture.buildGraph()
);
buildTimes.push(build.duration);
const find = measureCall('pathfinding-realmap-find', () =>
fixture.system.getPath(endpoints.target)
);
findTimes.push(find.duration);
steps = find.value.length;
}
const searchAvg =
findTimes.reduce(
(sum, t, idx) => sum + (t - buildTimes[idx]!),
0
) / REAL_RUNS;
// 审查要求输出结构化真实地图性能数据供汇报使用
// eslint-disable-next-line no-console
console.log(
`[pathfinding-realmap] map=${i + 1} 13x13 ` +
`start=(${endpoints.start.x},${endpoints.start.y}) ` +
`target=(${endpoints.target.x},${endpoints.target.y}) ` +
`steps=${steps} build=${minAvg(buildTimes)} ` +
`find=${minAvg(findTimes)} search=${searchAvg.toFixed(2)}ms ` +
`unreachable=${steps === 0 ? 'yes' : 'no'}`
);
// 真实地图不保证可达,仅对小图的耗时作宽松把关,不可达属有效数据
expect(Math.max(...findTimes)).toBeLessThan(500);
}
});
});