BFS and Dijkstra expand across the same terrain, drawn side by side as growing frontiers rather than finished paths. BFS’s frontier is a clean ring — it only counts steps, so terrain never bends it. Dijkstra’s frontier is a lumpy contour that bulges through cheap terrain and stalls against expensive terrain, because it’s actually adding up cost.
The control is a single weight-spread slider. At zero, every cell costs the same, so the two frontiers are identical and both paths cost the same. Drag the slider up and the terrain’s cost differences get amplified — Dijkstra reroutes around the expensive patches, while BFS (which never looked at cost in the first place) keeps walking the same straight-line path it always found. A pink tick on the slider marks the exact spread — found by binary search, not eyeballed — where BFS’s path first stops being the cheapest one. Past that point the cost readout below the grids turns red.
Hit R to reroll the terrain; the threshold tick moves with it.
Reuse
src/frontier.js is a framework-free ES module:
generateTerrain(cols, rows, rng)— a smoothed random field in[0, 1]per cell (a few box-blur passes over noise), used as the “expensiveness” driving cost.bfs(cols, rows, start, goal)/dijkstra(cols, rows, costFn, start, goal)— both return{ path, visitedOrder }(Dijkstra also returnscost);visitedOrderis the pop order, same shape asastar-grid’s, for animated replay on any renderer.pathCost(path, field, cols, spread, K)— cost of a path under a given spread, without re-running search; that’s what makes the threshold search cheap.findThreshold(...)— binary-searches the spread where BFS’s (fixed) path cost first exceeds Dijkstra’s minimum cost by more than a small epsilon. Returnsnullif they never diverge across[0, maxSpread].
No dependencies, no rendering or timers in the module — canvas drawing and
the slider/keyboard controls in demo/ are reference code, not the reusable
piece.
Gotchas
- There are no walls — every cell is traversable, only its entry cost
varies (
1 + spread * K * e). Cost, not connectivity, is the whole point. - BFS’s path and visit order are independent of spread by construction (BFS never reads the cost field), so it’s computed once per terrain and reused across every slider position — only Dijkstra re-runs live.
findThresholdassumes the cost gap is non-decreasing in spread, which holds for how this demo scales cost but isn’t true of every possible cost function; don’t reuse it blind on a differently-shaped cost model.- The demo bundles its own copy of
frontier.js(self-contained by contract); re-copy after editingsrc/.