diff --git a/src/Poe2Trade.Navigation/WorldMap.cs b/src/Poe2Trade.Navigation/WorldMap.cs
index 7dcd80e..9b837f8 100644
--- a/src/Poe2Trade.Navigation/WorldMap.cs
+++ b/src/Poe2Trade.Navigation/WorldMap.cs
@@ -364,9 +364,10 @@ public class WorldMap : IDisposable
}
///
- /// BFS through walkable (Explored) cells to find the nearest frontier
- /// (Explored cell adjacent to Unknown/Fog). Returns direction toward the
- /// first step on the shortest path, respecting walls.
+ /// BFS through walkable (Explored) cells to find the best frontier direction.
+ /// Instead of stopping at the nearest frontier, runs the full BFS and counts
+ /// frontier cells reachable per first-step direction. Prefers directions with
+ /// more frontier cells (corridors) over directions with few (dead-end rooms).
///
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 400)
{
@@ -379,24 +380,24 @@ public class WorldMap : IDisposable
var rr = searchRadius / step;
var gridW = 2 * rr + 1;
- // Visited grid + parent tracking (encode parent as single int)
var visited = new bool[gridW * gridW];
- var parentX = new short[gridW * gridW];
- var parentY = new short[gridW * gridW];
+ // Propagate first step from start during BFS (avoids per-frontier trace-back)
+ var firstStepX = new short[gridW * gridW];
+ var firstStepY = new short[gridW * gridW];
var queue = new Queue<(int gx, int gy)>(4096);
var startGx = rr;
var startGy = rr;
visited[startGy * gridW + startGx] = true;
- parentX[startGy * gridW + startGx] = -1;
- parentY[startGy * gridW + startGx] = -1;
queue.Enqueue((startGx, startGy));
// 8-connected neighbors
ReadOnlySpan dxs = [-1, 0, 1, -1, 1, -1, 0, 1];
ReadOnlySpan dys = [-1, -1, -1, 0, 0, 1, 1, 1];
- int foundGx = -1, foundGy = -1;
+ // Count frontier cells per first-step direction
+ var frontierCounts = new Dictionary();
+ var firstStepCoords = new Dictionary();
while (queue.Count > 0)
{
@@ -407,8 +408,9 @@ public class WorldMap : IDisposable
var wy = cy + (gy - rr) * step;
// Check if this explored cell borders Unknown/Fog (= frontier)
- if (gx != startGx || gy != startGy) // skip player cell
+ if (gx != startGx || gy != startGy)
{
+ var cellIdx = gy * gridW + gx;
for (var d = 0; d < 8; d++)
{
var nx = wx + dxs[d] * step;
@@ -417,9 +419,15 @@ public class WorldMap : IDisposable
var neighbor = _canvas.At(ny, nx);
if (neighbor == (byte)MapCell.Unknown || neighbor == (byte)MapCell.Fog)
{
- foundGx = gx;
- foundGy = gy;
- goto Found;
+ var fsKey = firstStepY[cellIdx] * gridW + firstStepX[cellIdx];
+ if (frontierCounts.TryGetValue(fsKey, out var cnt))
+ frontierCounts[fsKey] = cnt + 1;
+ else
+ {
+ frontierCounts[fsKey] = 1;
+ firstStepCoords[fsKey] = (firstStepX[cellIdx], firstStepY[cellIdx]);
+ }
+ break; // don't double-count this cell
}
}
}
@@ -439,43 +447,54 @@ public class WorldMap : IDisposable
if (nwx < 0 || nwx >= size || nwy < 0 || nwy >= size) continue;
var cell = _canvas.At(nwy, nwx);
- if (cell != (byte)MapCell.Explored) continue; // only walk through explored
+ if (cell != (byte)MapCell.Explored) continue;
visited[idx] = true;
- parentX[idx] = (short)gx;
- parentY[idx] = (short)gy;
+ // Propagate first step: direct neighbors of start ARE the first step
+ if (gx == startGx && gy == startGy)
+ {
+ firstStepX[idx] = (short)ngx;
+ firstStepY[idx] = (short)ngy;
+ }
+ else
+ {
+ var parentIdx = gy * gridW + gx;
+ firstStepX[idx] = firstStepX[parentIdx];
+ firstStepY[idx] = firstStepY[parentIdx];
+ }
queue.Enqueue((ngx, ngy));
}
}
- Log.Information("BFS: no reachable frontier within {Radius}px", searchRadius);
- return null;
-
- Found:
- // Trace back to first step from start
- var traceX = foundGx;
- var traceY = foundGy;
- while (true)
+ if (frontierCounts.Count == 0)
{
- var idx = traceY * gridW + traceX;
- var px = parentX[idx];
- var py = parentY[idx];
- if (px == startGx && py == startGy)
- break; // traceX/traceY is the first step
- traceX = px;
- traceY = py;
+ Log.Information("BFS: no reachable frontier within {Radius}px", searchRadius);
+ return null;
}
- var dirX = (double)(traceX - startGx);
- var dirY = (double)(traceY - startGy);
+ // Pick direction with the most frontier cells (prefers corridors over dead ends)
+ var bestKey = -1;
+ var bestCount = 0;
+ foreach (var (key, count) in frontierCounts)
+ {
+ if (count > bestCount)
+ {
+ bestCount = count;
+ bestKey = key;
+ }
+ }
+
+ var (bestGx, bestGy) = firstStepCoords[bestKey];
+ var dirX = (double)(bestGx - startGx);
+ var dirY = (double)(bestGy - startGy);
var len = Math.Sqrt(dirX * dirX + dirY * dirY);
- if (len < 0.001) return (1, 0); // shouldn't happen
+ if (len < 0.001) return (1, 0);
dirX /= len;
dirY /= len;
- var dist = Math.Sqrt((foundGx - startGx) * (foundGx - startGx) + (foundGy - startGy) * (foundGy - startGy)) * step;
- Log.Debug("BFS: frontier at {Dist:F0}px, first step dir=({Dx:F2},{Dy:F2})", dist, dirX, dirY);
+ Log.Debug("BFS: {DirCount} directions, best={Best} frontier cells, dir=({Dx:F2},{Dy:F2})",
+ frontierCounts.Count, bestCount, dirX, dirY);
return (dirX, dirY);
}
@@ -504,7 +523,8 @@ public class WorldMap : IDisposable
colored.Set(r, c, new Vec3b(104, 64, 31));
else if (v == (byte)MapCell.Wall)
colored.Set(r, c, new Vec3b(26, 45, 61));
- // Fog not rendered — too noisy from spell effects bleeding through overlay
+ else if (v == (byte)MapCell.Fog)
+ colored.Set(r, c, new Vec3b(55, 40, 28));
}
var px = cx - x0;