diff --git a/gameblocks/modules/behavior/GridPathPlanner.js b/gameblocks/modules/behavior/GridPathPlanner.js index c352caf..6672984 100644 --- a/gameblocks/modules/behavior/GridPathPlanner.js +++ b/gameblocks/modules/behavior/GridPathPlanner.js @@ -24,7 +24,11 @@ export function normalizeBlockedCells(blocked = []) { } function wrapDelta(delta, size) { - return Math.min(Math.abs(delta), size - Math.abs(delta)); + if (!Number.isFinite(size) || size <= 0) return 0; + const period = size; + let wrapped = delta % period; + if (wrapped < 0) wrapped += period; + return Math.min(wrapped, period - wrapped); } function priorityInsert(open, entry) { diff --git a/gameblocks/modules/behavior/GridPathPlanner.test.js b/gameblocks/modules/behavior/GridPathPlanner.test.js new file mode 100644 index 0000000..123f9c3 --- /dev/null +++ b/gameblocks/modules/behavior/GridPathPlanner.test.js @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { GridPathPlanner } from './GridPathPlanner.js'; + +const navigation = { + vectors: { + north: { right: 0, forward: 1 }, + east: { right: 1, forward: 0 }, + south: { right: 0, forward: -1 }, + west: { right: -1, forward: 0 }, + }, + neighborOrder: ['north', 'east', 'south', 'west'], +}; + +test('wrap heuristic uses torus distance for in-range cells', () => { + const planner = new GridPathPlanner({ navigation, columns: 10, rows: 10, wrap: true }); + assert.equal(planner.heuristic({ right: 0, forward: 0 }, { right: 9, forward: 0 }), 1); + assert.equal(planner.heuristic({ right: 0, forward: 0 }, { right: 1, forward: 0 }), 1); +}); + +test('wrap heuristic stays non-negative when cells are outside the board', () => { + const planner = new GridPathPlanner({ navigation, columns: 10, rows: 10, wrap: true }); + const cost = planner.heuristic({ right: 0, forward: 0 }, { right: 15, forward: 0 }); + assert.equal(cost, 5); + assert.ok(cost >= 0); +});