fix(char): move() clicked into the HUD and toggled the loot filter

Reported after Larzuk trips: the bot pressed escape, then flipped the loot
filter. Larzuk stands on the left of Harrogath, so moves to and from him aim at
the bottom-left corner — where D2R puts the seven loot-filter category buttons
(screen x 395-560, y 692-712). A right-click there toggles a filter, which
changes what renders and therefore what every later template match can see.

IChar.move() applied NO HUD avoidance in either branch:

    # teleport
    mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, ...)
    mouse.click(button="right")
    # walk
    x, y = convert_abs_to_monitor(pos_abs)
    mouse.move(x, y, randomize=5, ...)

The pather's anti-stuck path already called get_closest_non_hud_pixel; move()
never did, and assets/hud_mask.png covers those buttons correctly — the mask
was simply not consulted.

Latent for a long time and surfaced by Enigma. The walk branch shrinks its
target toward centre via adjust_factor, which mostly kept clicks off the HUD by
accident; the teleport branch clicks the raw target, so any low aim point lands
on the interface.

The escape that precedes it is unrelated and correct — common.close() dismissing
the repair panel.

Verified: filter-row targets are moved from y=700 to y=508, a centre target is
returned unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
alexpolo1
2026-08-28 14:57:18 +02:00
co-authored by Claude Opus 5
parent 74c3050163
commit 91e201c5f6
2 changed files with 62 additions and 0 deletions
+12
View File
@@ -232,6 +232,18 @@ class IChar:
Logger.error("timeout waiting for tele skill to activate")
def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_move: bool = False):
# Never click into the HUD. Neither branch below did this, so a move
# target low on the screen landed on the interface — most visibly the
# loot-filter toggles at the bottom-left (screen x 395-560, y 692-712),
# which a right-click flips. Reported after Larzuk trips: he stands on
# the left of Harrogath, so moves to and from him aim at that corner.
#
# Latent for a long time and surfaced by Enigma: the walk branch shrinks
# its target toward centre via adjust_factor, which mostly kept clicks
# off the HUD by accident, while the teleport branch clicks the raw
# target. The pather's anti-stuck path already guarded this; move() did
# not.
pos_monitor = get_closest_non_hud_pixel(pos_monitor, "monitor")
factor = Config().advanced_options["pathing_delay_factor"]
if "teleport" in Config().char and Config().char["teleport"] and (
force_tp
+50
View File
@@ -0,0 +1,50 @@
"""move() must never click into the HUD.
Reported 2026-08-28: after a Larzuk trip the bot pressed escape and then
toggled the LOOT FILTER. Larzuk stands on the left of Harrogath, so moves to
and from him aim at the bottom-left corner where D2R puts the seven
loot-filter category buttons (screen x 395-560, y 692-712). A right-click there
flips a filter, which changes what renders and therefore what every later
template match can see.
IChar.move() applied no HUD avoidance in either branch. The pather's anti-stuck
path already called get_closest_non_hud_pixel; move() did not.
Latent for a long time and surfaced by Enigma: the walk branch shrinks its
target toward centre via adjust_factor, which mostly kept clicks off the HUD by
accident, while the teleport branch clicks the raw target.
"""
import inspect
FILTER_ROW = [(400, 700), (470, 700), (550, 700), (470, 710)]
def test_hud_mask_covers_the_loot_filter_buttons():
import cv2
mask = cv2.imread("assets/hud_mask.png", cv2.IMREAD_GRAYSCALE)
mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)[1]
for x, y in FILTER_ROW:
assert mask[y, x] == 0, f"loot-filter button ({x},{y}) is not masked as HUD"
def test_targets_on_the_filter_row_are_moved_off_it():
from ui_manager import get_closest_non_hud_pixel
for x, y in FILTER_ROW:
ox, oy = get_closest_non_hud_pixel((x + 5, y + 98), "monitor") # monitor offset
assert (oy - 98) < 690, f"({x},{y}) still lands on the HUD at y={oy - 98}"
def test_a_centre_target_is_left_alone():
"""The guard must not perturb ordinary targets."""
from ui_manager import get_closest_non_hud_pixel
assert get_closest_non_hud_pixel((645, 458), "monitor") == (645, 458)
def test_move_applies_the_guard():
from char.i_char import IChar
src = inspect.getsource(IChar.move)
assert "get_closest_non_hud_pixel" in src, "move() can still click into the HUD"
guard_at = src.index("get_closest_non_hud_pixel")
first_click = min([i for i in (src.find("mouse.move"), src.find("mouse.click")) if i != -1])
assert guard_at < first_click, "the guard must run before any click"