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:
@@ -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)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user