minimap quickness

This commit is contained in:
Boki 2026-02-16 14:11:04 -05:00
parent d80e723b94
commit 3087f9146e
3 changed files with 65 additions and 26 deletions

BIN
assets/toc_finish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

View file

@ -13,6 +13,8 @@ public class MinimapCapture : IFrameConsumer, IDisposable
private readonly IScreenCapture _backend; // kept for debug capture paths private readonly IScreenCapture _backend; // kept for debug capture paths
private int _modeCheckCounter = 9; // trigger mode detection on first frame private int _modeCheckCounter = 9; // trigger mode detection on first frame
private MinimapMode _detectedMode = MinimapMode.Overlay; private MinimapMode _detectedMode = MinimapMode.Overlay;
private int _pendingModeCount; // consecutive detections of a different mode
private MinimapMode _pendingMode;
private MinimapFrame? _lastFrame; private MinimapFrame? _lastFrame;
public MinimapMode DetectedMode => _detectedMode; public MinimapMode DetectedMode => _detectedMode;
@ -31,23 +33,48 @@ public class MinimapCapture : IFrameConsumer, IDisposable
/// </summary> /// </summary>
public void Process(ScreenFrame screen) public void Process(ScreenFrame screen)
{ {
// Auto-detect minimap mode every 10th frame via single pixel probe // Auto-detect minimap mode by checking orange player dot at both positions.
// null = not in gameplay (loading/menu) → skip frame entirely.
// Require 3 consecutive detections of a new mode to avoid transient flips.
if (++_modeCheckCounter >= 10) if (++_modeCheckCounter >= 10)
{ {
_modeCheckCounter = 0; _modeCheckCounter = 0;
var detected = DetectMinimapMode(screen); var detected = DetectMinimapMode(screen);
if (detected == null)
{
_pendingModeCount = 0;
_lastFrame = null;
return; // not in gameplay — skip frame
}
if (detected != _detectedMode) if (detected != _detectedMode)
{ {
var oldMode = _detectedMode; if (detected == _pendingMode)
var oldRegion = _detectedMode == MinimapMode.Overlay ? _config.OverlayRegion : _config.CornerRegion; _pendingModeCount++;
_detectedMode = detected; else
var newRegion = _detectedMode == MinimapMode.Overlay ? _config.OverlayRegion : _config.CornerRegion; {
Log.Information("MODE SWITCH: {Old} → {New} | oldRegion=({OX},{OY},{OW}x{OH}) newRegion=({NX},{NY},{NW}x{NH})", _pendingMode = detected.Value;
oldMode, _detectedMode, _pendingModeCount = 1;
oldRegion.X, oldRegion.Y, oldRegion.Width, oldRegion.Height, }
newRegion.X, newRegion.Y, newRegion.Width, newRegion.Height);
ResetAdaptation(); if (_pendingModeCount >= 3)
ModeChanged?.Invoke(_detectedMode); {
var oldMode = _detectedMode;
var oldRegion = _detectedMode == MinimapMode.Overlay ? _config.OverlayRegion : _config.CornerRegion;
_detectedMode = detected.Value;
var newRegion = _detectedMode == MinimapMode.Overlay ? _config.OverlayRegion : _config.CornerRegion;
Log.Information("MODE SWITCH: {Old} → {New} | oldRegion=({OX},{OY},{OW}x{OH}) newRegion=({NX},{NY},{NW}x{NH})",
oldMode, _detectedMode,
oldRegion.X, oldRegion.Y, oldRegion.Width, oldRegion.Height,
newRegion.X, newRegion.Y, newRegion.Width, newRegion.Height);
ResetAdaptation();
_pendingModeCount = 0;
ModeChanged?.Invoke(_detectedMode);
}
}
else
{
_pendingModeCount = 0;
} }
} }
@ -137,19 +164,24 @@ public class MinimapCapture : IFrameConsumer, IDisposable
} }
/// <summary> /// <summary>
/// Detect minimap mode by sampling pixels at the corner minimap center. /// Detect minimap mode by sampling the orange player dot (#DE581B) at both
/// If the pixel is close to #DE581B (orange player dot), corner minimap is active. /// the overlay center and corner center. Returns null if neither is found
/// (loading screen, menu, map transition).
/// </summary> /// </summary>
private MinimapMode DetectMinimapMode(ScreenFrame screen) private MinimapMode? DetectMinimapMode(ScreenFrame screen)
{ {
var cx = _config.CornerCenterX; if (IsOrangeDot(screen, _config.OverlayCenterX, _config.OverlayCenterY))
var cy = _config.CornerCenterY; return MinimapMode.Overlay;
if (IsOrangeDot(screen, _config.CornerCenterX, _config.CornerCenterY))
return MinimapMode.Corner;
return null;
}
// Bounds check private static bool IsOrangeDot(ScreenFrame screen, int cx, int cy)
{
if (cx < 2 || cy < 2 || cx + 2 >= screen.Width || cy + 2 >= screen.Height) if (cx < 2 || cy < 2 || cx + 2 >= screen.Width || cy + 2 >= screen.Height)
return _detectedMode; return false;
// Average a 5x5 patch worth of pixels
double bSum = 0, gSum = 0, rSum = 0; double bSum = 0, gSum = 0, rSum = 0;
var count = 0; var count = 0;
for (var dy = -2; dy <= 2; dy++) for (var dy = -2; dy <= 2; dy++)
@ -162,16 +194,13 @@ public class MinimapCapture : IFrameConsumer, IDisposable
count++; count++;
} }
var b = bSum / count;
var g = gSum / count;
var r = rSum / count; var r = rSum / count;
var g = gSum / count;
var b = bSum / count;
// #DE581B → R=222, G=88, B=27 // #DE581B → R=222, G=88, B=27
const int tol = 60; const int tol = 60;
if (Math.Abs(r - 222) < tol && Math.Abs(g - 88) < tol && Math.Abs(b - 27) < tol) return Math.Abs(r - 222) < tol && Math.Abs(g - 88) < tol && Math.Abs(b - 27) < tol;
return MinimapMode.Corner;
return MinimapMode.Overlay;
} }
private Mat BuildWallMask(Mat hsv, Mat playerMask, bool sample = false) private Mat BuildWallMask(Mat hsv, Mat playerMask, bool sample = false)

View file

@ -93,8 +93,17 @@ public class WorldMap : IDisposable
// Warmup / re-bootstrap: stitch at current position to seed the canvas // Warmup / re-bootstrap: stitch at current position to seed the canvas
if (needsBootstrap) if (needsBootstrap)
{ {
// Don't consume warmup slots on empty frames (game still loading minimap)
if (wallCountAfter < 50)
{
_frameCount--;
Log.Information("Warmup waiting for minimap ({Ms:F1}ms)", sw.Elapsed.TotalMilliseconds);
return _position;
}
StitchWithConfidence(classifiedMat, _position, boosted: true, mode: mode); StitchWithConfidence(classifiedMat, _position, boosted: true, mode: mode);
PaintExploredCircle(_position); PaintExploredCircle(_position);
LastMatchSucceeded = true; // signal caller to update viewport
if (_consecutiveMatchFails >= 30) if (_consecutiveMatchFails >= 30)
{ {
Log.Information("Re-bootstrap: mode={Mode} pos=({X:F1},{Y:F1}) frameSize={FS} walls={W} stitch={Ms:F1}ms", Log.Information("Re-bootstrap: mode={Mode} pos=({X:F1},{Y:F1}) frameSize={FS} walls={W} stitch={Ms:F1}ms",
@ -471,6 +480,7 @@ public class WorldMap : IDisposable
_position = new MapPosition(_canvasSize / 2.0, _canvasSize / 2.0); _position = new MapPosition(_canvasSize / 2.0, _canvasSize / 2.0);
_frameCount = 0; _frameCount = 0;
_consecutiveMatchFails = 0; _consecutiveMatchFails = 0;
LastMatchSucceeded = false;
} }
/// <summary> /// <summary>
@ -520,7 +530,7 @@ public class WorldMap : IDisposable
_prevWallMask?.Dispose(); _prevWallMask?.Dispose();
_prevWallMask = null; _prevWallMask = null;
// Don't force re-bootstrap — let the match find the correct position first _frameCount = 0; // force re-warmup with new mode's data
} }
public void Dispose() public void Dispose()