From bb8e75a2f5d7c4654252cf764789662a913854ad Mon Sep 17 00:00:00 2001 From: Boki Date: Fri, 13 Feb 2026 13:14:09 -0500 Subject: [PATCH] getting closer --- src/Poe2Trade.Navigation/NavigationTypes.cs | 12 +++- src/Poe2Trade.Navigation/WorldMap.cs | 68 ++++++++++++++++++--- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/Poe2Trade.Navigation/NavigationTypes.cs b/src/Poe2Trade.Navigation/NavigationTypes.cs index 87540e7..42179f4 100644 --- a/src/Poe2Trade.Navigation/NavigationTypes.cs +++ b/src/Poe2Trade.Navigation/NavigationTypes.cs @@ -95,11 +95,21 @@ public class MinimapConfig public int CanvasSize { get; set; } = 4000; // Template matching: search radius around current position estimate (pixels) - public int MatchSearchRadius { get; set; } = 50; + public int MatchSearchRadius { get; set; } = 100; // Template matching: minimum correlation confidence to accept a match public double MatchConfidence { get; set; } = 0.3; + // Wall confidence (canvas-level): per-pixel counters to filter transient noise + public int ConfidenceInc { get; set; } = 3; + public int ConfidenceDec { get; set; } = 1; + public int ConfidenceThreshold { get; set; } = 8; + public int ConfidenceMax { get; set; } = 30; + public int WarmupFrames { get; set; } = 5; + + // Frame dedup: min changed pixels to process a frame (skip near-identical minimap frames) + public int FrameChangeThreshold { get; set; } = 50; + // Stuck detection public double StuckThreshold { get; set; } = 2.0; public int StuckFrameCount { get; set; } = 5; diff --git a/src/Poe2Trade.Navigation/WorldMap.cs b/src/Poe2Trade.Navigation/WorldMap.cs index 130643a..56ccbdd 100644 --- a/src/Poe2Trade.Navigation/WorldMap.cs +++ b/src/Poe2Trade.Navigation/WorldMap.cs @@ -7,8 +7,10 @@ public class WorldMap : IDisposable { private readonly MinimapConfig _config; private readonly Mat _canvas; + private readonly Mat _confidence; // CV_16SC1: per-pixel wall confidence counter private MapPosition _position; private int _frameCount; + private Mat? _prevWallMask; // for frame deduplication public MapPosition Position => _position; @@ -16,6 +18,7 @@ public class WorldMap : IDisposable { _config = config; _canvas = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_8UC1, Scalar.Black); + _confidence = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_16SC1, Scalar.Black); _position = new MapPosition(config.CanvasSize / 2.0, config.CanvasSize / 2.0); } @@ -27,19 +30,37 @@ public class WorldMap : IDisposable { _frameCount++; - // First frame: just stitch at center - if (_frameCount <= 1) + // Frame deduplication: skip if minimap hasn't scrolled yet + if (_prevWallMask != null && _frameCount > 1) { - Stitch(classifiedMat, _position); + using var xor = new Mat(); + Cv2.BitwiseXor(wallMask, _prevWallMask, xor); + var changedPixels = Cv2.CountNonZero(xor); + if (changedPixels < _config.FrameChangeThreshold) + { + Log.Debug("Frame dedup: {Changed} changed pixels, skipping", changedPixels); + return _position; + } + } + + // Store current wall mask for next frame's dedup check + _prevWallMask?.Dispose(); + _prevWallMask = wallMask.Clone(); + + // Warmup: stitch at center with boosted confidence to bootstrap canvas + if (_frameCount <= _config.WarmupFrames) + { + StitchWithConfidence(classifiedMat, _position, boosted: true); return _position; } // Match wallMask against canvas to find best position var matched = MatchPosition(wallMask, _position); - if (matched != null) - _position = matched; + if (matched == null) + return _position; // skip stitching entirely on failed match - Stitch(classifiedMat, _position); + _position = matched; + StitchWithConfidence(classifiedMat, _position, boosted: false); return _position; } @@ -100,7 +121,7 @@ public class WorldMap : IDisposable return new MapPosition(matchX, matchY); } - private void Stitch(Mat classifiedMat, MapPosition position) + private void StitchWithConfidence(Mat classifiedMat, MapPosition position, bool boosted) { var halfSize = _config.CaptureSize / 2; var canvasX = (int)Math.Round(position.X) - halfSize; @@ -121,14 +142,38 @@ public class WorldMap : IDisposable var srcRoi = new Mat(classifiedMat, srcRect); var dstRoi = new Mat(_canvas, dstRect); + var confRoi = new Mat(_confidence, dstRect); + + var confInc = (short)_config.ConfidenceInc; + var confDec = (short)_config.ConfidenceDec; + var confThreshold = (short)_config.ConfidenceThreshold; + var confMax = (short)_config.ConfidenceMax; - // Paste wall pixels (walls always win) for (var row = 0; row < h; row++) for (var col = 0; col < w; col++) { var srcVal = srcRoi.At(row, col); + var conf = confRoi.At(row, col); + if (srcVal == (byte)MapCell.Wall) - dstRoi.Set(row, col, srcVal); + { + if (boosted) + conf = confThreshold; // warmup: immediately at threshold + else + conf = Math.Min((short)(conf + confInc), confMax); + } + else + { + // Pixel is in visible area but not wall — decay confidence + conf = Math.Max((short)(conf - confDec), (short)0); + } + + confRoi.Set(row, col, conf); + + if (conf >= confThreshold) + dstRoi.Set(row, col, (byte)MapCell.Wall); + else if (dstRoi.At(row, col) == (byte)MapCell.Wall) + dstRoi.Set(row, col, (byte)MapCell.Explored); // lost confidence, downgrade } // Mark explored area: circle around player, only overwrite Unknown @@ -243,6 +288,9 @@ public class WorldMap : IDisposable public void Reset() { _canvas.SetTo(Scalar.Black); + _confidence.SetTo(Scalar.Black); + _prevWallMask?.Dispose(); + _prevWallMask = null; _position = new MapPosition(_config.CanvasSize / 2.0, _config.CanvasSize / 2.0); _frameCount = 0; } @@ -250,5 +298,7 @@ public class WorldMap : IDisposable public void Dispose() { _canvas.Dispose(); + _confidence.Dispose(); + _prevWallMask?.Dispose(); } }