From 89242094f86532184a608b5ef8fa38caf0bf5e19 Mon Sep 17 00:00:00 2001 From: Arkadiusz Sygulski Date: Sun, 11 Jan 2026 23:35:05 +0100 Subject: [PATCH] Rabbit review --- src/core/pathfinding/PathFinder.Air.ts | 12 ++++- src/core/pathfinding/PathFinderStepper.ts | 13 +++-- .../pathfinding/algorithms/PriorityQueue.ts | 6 ++- .../transformers/ShoreCoercingTransformer.ts | 54 +++++-------------- tests/pathfinding/benchmark/compare.ts | 15 +++--- 5 files changed, 45 insertions(+), 55 deletions(-) diff --git a/src/core/pathfinding/PathFinder.Air.ts b/src/core/pathfinding/PathFinder.Air.ts index 51796a00c..504427643 100644 --- a/src/core/pathfinding/PathFinder.Air.ts +++ b/src/core/pathfinding/PathFinder.Air.ts @@ -47,10 +47,18 @@ export class AirPathFinder implements PathFinder { let nextY = y; const ratio = Math.floor(1 + Math.abs(dstY - y) / (Math.abs(dstX - x) + 1)); - if (random.chance(ratio) && x !== dstX) { + if (x === dstX) { + // Can only move in Y + nextY += y < dstY ? 1 : -1; + } else if (y === dstY) { + // Can only move in X nextX += x < dstX ? 1 : -1; } else { - nextY += y < dstY ? 1 : -1; + if (random.chance(ratio)) { + nextX += x < dstX ? 1 : -1; + } else { + nextY += y < dstY ? 1 : -1; + } } return this.game.ref(nextX, nextY); diff --git a/src/core/pathfinding/PathFinderStepper.ts b/src/core/pathfinding/PathFinderStepper.ts index f78505b26..4b8081fdc 100644 --- a/src/core/pathfinding/PathFinderStepper.ts +++ b/src/core/pathfinding/PathFinderStepper.ts @@ -102,9 +102,16 @@ export class PathFinderStepper implements SteppingPathFinder { findPath(from: T | T[], to: T): T[] | null { if (this.config.preCheck) { - const first = Array.isArray(from) ? from[0] : from; - const result = this.config.preCheck(first, to); - if (result?.status === PathStatus.NOT_FOUND) return null; + const fromArray = Array.isArray(from) ? from : [from]; + + const allFailed = fromArray.every((f) => { + const result = this.config.preCheck!(f, to); + return result?.status === PathStatus.NOT_FOUND; + }); + + if (allFailed) { + return null; + } } return this.finder.findPath(from, to); diff --git a/src/core/pathfinding/algorithms/PriorityQueue.ts b/src/core/pathfinding/algorithms/PriorityQueue.ts index a879dc2a3..c8f525f0b 100644 --- a/src/core/pathfinding/algorithms/PriorityQueue.ts +++ b/src/core/pathfinding/algorithms/PriorityQueue.ts @@ -11,12 +11,16 @@ export class MinHeap implements PriorityQueue { private priorities: Float32Array; private size = 0; - constructor(capacity: number) { + constructor(private capacity: number) { this.heap = new Int32Array(capacity); this.priorities = new Float32Array(capacity); } push(node: number, priority: number): void { + if (this.size >= this.capacity) { + throw new Error(`MinHeap capacity exceeded: ${this.capacity}`); + } + let i = this.size++; this.heap[i] = node; this.priorities[i] = priority; diff --git a/src/core/pathfinding/transformers/ShoreCoercingTransformer.ts b/src/core/pathfinding/transformers/ShoreCoercingTransformer.ts index 2a1f56f6c..1ba652db5 100644 --- a/src/core/pathfinding/transformers/ShoreCoercingTransformer.ts +++ b/src/core/pathfinding/transformers/ShoreCoercingTransformer.ts @@ -18,24 +18,19 @@ export class ShoreCoercingTransformer implements PathFinder { ) {} findPath(from: TileRef | TileRef[], to: TileRef): TileRef[] | null { - // Coerce from tiles const fromArray = Array.isArray(from) ? from : [from]; - const coercedFromArray: Array<{ - water: TileRef; - original: TileRef | null; - }> = []; + const waterToOriginal = new Map(); + const waterFrom: TileRef[] = []; for (const f of fromArray) { const coerced = this.coerceToWater(f); if (coerced.water !== null) { - coercedFromArray.push({ - water: coerced.water, - original: coerced.original, - }); + waterFrom.push(coerced.water); + waterToOriginal.set(coerced.water, coerced.original); } } - if (coercedFromArray.length === 0) { + if (waterFrom.length === 0) { return null; } @@ -45,52 +40,27 @@ export class ShoreCoercingTransformer implements PathFinder { return null; } - // Build water-only from array - const waterFrom = - coercedFromArray.length === 1 - ? coercedFromArray[0].water - : coercedFromArray.map((c) => c.water); - // Search on water tiles const path = this.inner.findPath(waterFrom, coercedTo.water); if (!path || path.length === 0) { return null; } - // Fix extremes: find which source was used and prepend/append originals - const result = [...path]; - - // Find the original for the source that was used (closest to path start) - if (coercedFromArray.length > 0) { - const pathStart = result[0]; - let bestOriginal: TileRef | null = null; - let minDist = Infinity; - - for (const { water, original } of coercedFromArray) { - if (original !== null) { - const dist = this.map.manhattanDist(pathStart, water); - if (dist < minDist) { - minDist = dist; - bestOriginal = original; - } - } - } - - // Prepend original if we have one and it's not already at start - if (bestOriginal !== null && result[0] !== bestOriginal) { - result.unshift(bestOriginal); - } + // Look up the actual path start in the map + const originalShore = waterToOriginal.get(path[0]); + if (originalShore !== undefined && originalShore !== null) { + path.unshift(originalShore); } // Append original to if different if ( coercedTo.original !== null && - result[result.length - 1] !== coercedTo.original + path[path.length - 1] !== coercedTo.original ) { - result.push(coercedTo.original); + path.push(coercedTo.original); } - return result; + return path; } /** diff --git a/tests/pathfinding/benchmark/compare.ts b/tests/pathfinding/benchmark/compare.ts index e69aadbb0..e39adb49d 100644 --- a/tests/pathfinding/benchmark/compare.ts +++ b/tests/pathfinding/benchmark/compare.ts @@ -8,8 +8,8 @@ * npx tsx tests/pathfinding/benchmark/compare.ts --synthetic * * Examples: - * npx tsx tests/pathfinding/benchmark/compare.ts default hpa,legacy - * npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,legacy,a.optimized + * npx tsx tests/pathfinding/benchmark/compare.ts default hpa,a.baseline + * npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,hpa.cached,a.full */ import { @@ -111,17 +111,18 @@ Usage: Arguments: Name of the scenario (default: "default") - Comma-separated list of adapters to compare (e.g., "hpa,legacy,a.optimized") + Comma-separated list of adapters to compare (e.g., "hpa,a.baseline") Examples: - npx tsx tests/pathfinding/benchmark/compare.ts default hpa,legacy - npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,legacy,a.optimized + npx tsx tests/pathfinding/benchmark/compare.ts default hpa,a.baseline + npx tsx tests/pathfinding/benchmark/compare.ts --synthetic giantworldmap hpa,hpa.cached,a.full Available adapters: + a.baseline - A* on minimap (inlined) + a.generic - A* on minimap (adapter) + a.full - A* on full map hpa - Hierarchical pathfinding (no cache) hpa.cached - Hierarchical pathfinding (with cache) - legacy - Legacy A* algorithm - a.optimized - Optimized A* algorithm `); }