test: scope the HUD-click invariant to every movement method, not just move()

walk() carried the identical unguarded click that move() had and was missed
because the tests were written against move() alone. This asserts the invariant
across the movement methods, so a future one is caught without anyone
remembering to extend the tests.

Deliberately NOT covered, because relocating these breaks what they do:

    pick_up_item         must click the item itself
    _remap_skill_hotkey  deliberately clicks the UI
    cast_in_arc          aims a cast direction, not a destination

Only clicks that choose a DESTINATION may be moved off the HUD. A first draft
of this test flagged all of the above and was wrong to; the distinction is
between "go here" and "hit that".

Verified by falsification: reverting walk() to randomize=5 fails with
"walk: mouse.move(x, y, randomize=5, ...)".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
alexpolo1
2026-08-28 16:57:01 +02:00
parent e6ba9ad830
commit ee39aa5dbf

View File

@@ -108,3 +108,41 @@ def test_move_clicks_with_randomization_disabled():
f"move() still randomizes after the guard, which can re-enter "
f"the HUD: {line.strip()}"
)
def test_every_movement_click_in_ichar_is_guarded():
"""Scope the invariant to every MOVEMENT method, not one of them.
The first version of this guard covered move(), and its tests asserted only
on move(). walk() carried the identical unguarded click and was missed — it
is reached from bot.py's walk-back-to-town path and from poison_necro, so it
was a live second route to the same loot-filter toggle.
Only movement clicks may be relocated. Clicks that must land on a specific
thing are deliberately excluded, because moving them breaks what they do:
pick_up_item must hit the item itself
_remap_skill_hotkey deliberately clicks the UI
cast_in_arc aims a cast direction, not a destination
"""
import inspect
from char.i_char import IChar
MOVEMENT = ("move", "walk")
offenders = []
for name in MOVEMENT:
fn = getattr(IChar, name, None)
if fn is None:
continue
src = inspect.getsource(fn)
body = chr(10).join(l for l in src.splitlines() if not l.strip().startswith("#"))
parts = body.split('"""')
body = parts[0] + "".join(parts[2:]) if len(parts) > 2 else parts[0]
for line in body.splitlines():
if "mouse.move(" in line and "randomize=0" not in line:
offenders.append(f"{name}: {line.strip()}")
assert not offenders, (
"movement clicks that can land on the HUD (must go through "
"_hud_safe_target then move with randomize=0): " + "; ".join(offenders)
)