@@ -207,7 +207,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
207207 const combinedScale = timeScale * debugScale;
208208 frameTimeDeltaMS *= combinedScale;
209209 frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
210- if (debugScale <= 1)
210+ if (combinedScale <= 1)
211211 frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
212212
213213 let wasUpdated = false;
@@ -14117,10 +14117,10 @@ class PathFinder
1411714117
1411814118 // Tunables (public, freely re-assignable).
1411914119 this.heuristicWeight = 1;
14120- this.maxLoop = 500 ;
14120+ this.maxLoop = 1e3 ;
1412114121 this.smoothPath = true;
1412214122 this.debug = false;
14123- this.debugTime = 2 ;
14123+ this.debugTime = 1 ;
1412414124
1412514125 // Pre-allocate the node array — one node per tile, reused across calls.
1412614126 this.nodes = new Array(this.size.x * this.size.y);
@@ -14296,9 +14296,12 @@ class PathFinder
1429614296 // Best path so far through neighbor — record it.
1429714297 neighbor.parent = current;
1429814298 neighbor.g = tentativeG;
14299- const gdx = endNode.pos.x - neighbor.pos.x;
14300- const gdy = endNode.pos.y - neighbor.pos.y;
14301- neighbor.f = neighbor.g + (gdx * gdx + gdy * gdy) * this.heuristicWeight;
14299+ // Octile heuristic — tightest admissible distance for an
14300+ // 8-connected grid with cardinal cost 1 and diagonal cost √2.
14301+ const adx = abs(endNode.pos.x - neighbor.pos.x);
14302+ const ady = abs(endNode.pos.y - neighbor.pos.y);
14303+ const h = max(adx, ady) + (Math.SQRT2 - 1) * min(adx, ady);
14304+ neighbor.f = neighbor.g + h * this.heuristicWeight;
1430214305 }
1430314306 }
1430414307
@@ -14580,6 +14583,24 @@ class PathFinder
1458014583 path.push(original[original.length - 1]);
1458114584 }
1458214585
14586+ /** Drop any middle node that lies exactly on the line through its two
14587+ * neighbors. Backstop for the smoothing passes — the corners pass
14588+ * intentionally keeps truly-straight runs, and the string-pulling pass
14589+ * checks collinearity against the original path, not the in-progress
14590+ * result, so it can leave 3+ collinear nodes in some edge cases.
14591+ * @param {PathFinderNode[]} path
14592+ * @private */
14593+ dropCollinearNodes(path)
14594+ {
14595+ for (let i = path.length - 2; i >= 1; --i)
14596+ {
14597+ const a = path[i - 1], b = path[i], c = path[i + 1];
14598+ if ((b.pos.x - a.pos.x) * (c.pos.y - a.pos.y) ===
14599+ (b.pos.y - a.pos.y) * (c.pos.x - a.pos.x))
14600+ path.splice(i, 1);
14601+ }
14602+ }
14603+
1458314604 /** Lookup helper: true when the node at tile coords (x, y) is in-bounds
1458414605 * and clear (walkable, zero-cost). Used by isLineClear's hot path.
1458514606 * @param {number} x
@@ -14747,6 +14768,7 @@ class PathFinder
1474714768 {
1474814769 this.smoothPathCorners(nodePath);
1474914770 this.smoothPathStringPull(nodePath);
14771+ this.dropCollinearNodes(nodePath);
1475014772 }
1475114773
1475214774 // Convert to world-space Vector2 path. Return copies, not live node
0 commit comments