diff --git a/src/Poe2Trade.Navigation/MinimapCapture.cs b/src/Poe2Trade.Navigation/MinimapCapture.cs
index 9f7d444..764e3b7 100644
--- a/src/Poe2Trade.Navigation/MinimapCapture.cs
+++ b/src/Poe2Trade.Navigation/MinimapCapture.cs
@@ -205,8 +205,9 @@ public class MinimapCapture : IFrameConsumer, IDisposable
private Mat BuildWallMask(Mat hsv, Mat playerMask, bool sample = false)
{
- var lo = _colorTracker.AdaptedLo ?? _config.WallLoHSV;
- var hi = _colorTracker.AdaptedHi ?? _config.WallHiHSV;
+ var isCorner = _detectedMode == MinimapMode.Corner;
+ var lo = isCorner ? _config.CornerWallLoHSV : (_colorTracker.AdaptedLo ?? _config.WallLoHSV);
+ var hi = isCorner ? _config.CornerWallHiHSV : (_colorTracker.AdaptedHi ?? _config.WallHiHSV);
var wallMask = new Mat();
Cv2.InRange(hsv, lo, hi, wallMask);
@@ -215,7 +216,7 @@ public class MinimapCapture : IFrameConsumer, IDisposable
Cv2.BitwiseNot(playerMask, notPlayer);
Cv2.BitwiseAnd(wallMask, notPlayer, wallMask);
- if (sample)
+ if (sample && !isCorner)
_colorTracker.SampleFrame(hsv, wallMask);
using var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
diff --git a/src/Poe2Trade.Navigation/NavigationTypes.cs b/src/Poe2Trade.Navigation/NavigationTypes.cs
index 610ebb5..02ca91e 100644
--- a/src/Poe2Trade.Navigation/NavigationTypes.cs
+++ b/src/Poe2Trade.Navigation/NavigationTypes.cs
@@ -98,11 +98,15 @@ public class MinimapConfig
public Scalar PlayerLoHSV { get; set; } = new(5, 80, 80);
public Scalar PlayerHiHSV { get; set; } = new(25, 255, 255);
- // Wall detection: target #A2AEE5 (blue-lavender structure lines)
+ // Wall detection (overlay): target #A2AEE5 (blue-lavender structure lines)
// HSV(115, 75, 229) — blue hue, low-medium saturation, bright
public Scalar WallLoHSV { get; set; } = new(110, 25, 190);
public Scalar WallHiHSV { get; set; } = new(136, 120, 255);
+ // Wall detection (corner): wider V range for faded walls
+ public Scalar CornerWallLoHSV { get; set; } = new(110, 25, 100);
+ public Scalar CornerWallHiHSV { get; set; } = new(136, 120, 255);
+
// Connected components: minimum area to keep (kills speckle)
public int WallMinArea { get; set; } = 30;
diff --git a/src/Poe2Trade.Navigation/PathFinder.cs b/src/Poe2Trade.Navigation/PathFinder.cs
index 5a77691..77b623f 100644
--- a/src/Poe2Trade.Navigation/PathFinder.cs
+++ b/src/Poe2Trade.Navigation/PathFinder.cs
@@ -3,11 +3,24 @@ using Serilog;
namespace Poe2Trade.Navigation;
+///
+/// Last BFS result for visualization.
+///
+internal record BfsResult(
+ List BestFrontier, // frontier cells in the chosen direction (canvas coords)
+ List OtherFrontier, // frontier cells in other directions (canvas coords)
+ double DirX, double DirY, // chosen direction (unit vector)
+ int PlayerCx, int PlayerCy // player position on canvas
+);
+
///
/// BFS pathfinding through the world map canvas. Pure function — reads canvas, never modifies it.
///
internal class PathFinder
{
+ /// Last BFS result for viewport overlay.
+ public BfsResult? LastResult { get; private set; }
+
///
/// BFS through walkable (Explored) cells to find the best frontier direction.
/// Instead of stopping at the nearest frontier, runs the full BFS and counts
@@ -40,9 +53,10 @@ internal class PathFinder
ReadOnlySpan dxs = [-1, 0, 1, -1, 1, -1, 0, 1];
ReadOnlySpan dys = [-1, -1, -1, 0, 0, 1, 1, 1];
- // Count frontier cells per first-step direction
+ // Count frontier cells per first-step direction, and collect positions
var frontierCounts = new Dictionary();
var firstStepCoords = new Dictionary();
+ var frontierByKey = new Dictionary>();
while (queue.Count > 0)
{
@@ -71,7 +85,9 @@ internal class PathFinder
{
frontierCounts[fsKey] = 1;
firstStepCoords[fsKey] = (firstStepX[cellIdx], firstStepY[cellIdx]);
+ frontierByKey[fsKey] = [];
}
+ frontierByKey[fsKey].Add(new Point(wx, wy));
break; // don't double-count this cell
}
}
@@ -114,6 +130,7 @@ internal class PathFinder
if (frontierCounts.Count == 0)
{
Log.Information("BFS: no reachable frontier within {Radius}px", searchRadius);
+ LastResult = null;
return null;
}
@@ -138,6 +155,16 @@ internal class PathFinder
dirX /= len;
dirY /= len;
+ // Store result for visualization
+ var bestFrontier = frontierByKey[bestKey];
+ var otherFrontier = new List();
+ foreach (var (key, pts) in frontierByKey)
+ {
+ if (key != bestKey)
+ otherFrontier.AddRange(pts);
+ }
+ LastResult = new BfsResult(bestFrontier, otherFrontier, dirX, dirY, cx, cy);
+
Log.Debug("BFS: {DirCount} directions, best={Best} frontier cells, dir=({Dx:F2},{Dy:F2})",
frontierCounts.Count, bestCount, dirX, dirY);
return (dirX, dirY);
diff --git a/src/Poe2Trade.Navigation/WorldMap.cs b/src/Poe2Trade.Navigation/WorldMap.cs
index 1fe21ec..3d545c5 100644
--- a/src/Poe2Trade.Navigation/WorldMap.cs
+++ b/src/Poe2Trade.Navigation/WorldMap.cs
@@ -459,6 +459,39 @@ public class WorldMap : IDisposable
colored.Set(r, c, new Vec3b(55, 40, 28));
}
+ // BFS overlay: frontier cells + direction line
+ var bfs = _pathFinder.LastResult;
+ if (bfs != null)
+ {
+ // Other frontier directions (dim cyan)
+ foreach (var pt in bfs.OtherFrontier)
+ {
+ var vx = pt.X - x0;
+ var vy = pt.Y - y0;
+ if (vx >= 0 && vx < viewSize && vy >= 0 && vy < viewSize)
+ colored.Set(vy, vx, new Vec3b(100, 80, 0));
+ }
+
+ // Best frontier direction (bright green)
+ foreach (var pt in bfs.BestFrontier)
+ {
+ var vx = pt.X - x0;
+ var vy = pt.Y - y0;
+ if (vx >= 0 && vx < viewSize && vy >= 0 && vy < viewSize)
+ colored.Set(vy, vx, new Vec3b(0, 220, 0));
+ }
+
+ // Direction line from player
+ var px2 = bfs.PlayerCx - x0;
+ var py2 = bfs.PlayerCy - y0;
+ var lineLen = 60;
+ var ex = (int)(px2 + bfs.DirX * lineLen);
+ var ey = (int)(py2 + bfs.DirY * lineLen);
+ Cv2.ArrowedLine(colored, new Point(px2, py2), new Point(ex, ey),
+ new Scalar(0, 220, 0), 2, tipLength: 0.3);
+ }
+
+ // Player dot (orange, on top)
var px = cx - x0;
var py = cy - y0;
if (px >= 0 && px < viewSize && py >= 0 && py < viewSize)