Files
my-botty/test/town/a5_open_wp_test.py
alexpolo1andClaude Sonnet 4.6 e62272b743 fix: use -1.0 budget in time_budget test to survive Windows clock resolution
time.time() on Windows has ~15 ms resolution; a 0.0-second budget produced a
deadline equal to the current tick, so the anchor-loop check never fired and
traverse_calls reached 5 instead of 1. Using -1.0 puts the deadline one second
in the past — guaranteed expired on any hardware.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-08-01 09:11:27 +02:00

94 lines
3.1 KiB
Python

"""Regression tests for A5.open_wp fast-fail behavior.
Background: a stale curr_loc can land the char off the WP stone, so
select_by_template("A5_WP") never matches. The old escalation (NPC anchors +
a 6-step directed sweep) could loop for 5+ minutes, stranding the bot in town
("stuck in town"). open_wp now bails within a hard wall-clock budget and drops
the directed sweep, so the caller's fallback (buy at Malah / skip to stash)
takes over. See log/log.txt 2026-06-24.
"""
import town.a5 as a5_module
from town.a5 import A5
from pather import Location
class _Match:
def __init__(self, valid):
self.valid = valid
class _FakePather:
"""Pather whose node paths always 'succeed' (return True) — mirrors the real
failure mode where traverse returns True but the char is off the WP stone."""
def __init__(self):
self.traverse_calls = 0
def traverse_nodes(self, path, char, **kwargs):
self.traverse_calls += 1
return True
class _FakeChar:
def __init__(self):
self.select_calls = 0
def move(self, *args, **kwargs):
pass
def select_by_template(self, *args, **kwargs):
# The WP stone is never clickable in this scenario.
self.select_calls += 1
return False
def _patch_screen(monkeypatch):
# template_finder.search must report "WP not on screen" and grab()/is_visible
# must be cheap no-ops so the test never touches the real screen.
monkeypatch.setattr(a5_module.template_finder, "search", lambda *a, **k: _Match(False))
monkeypatch.setattr(a5_module, "grab", lambda *a, **k: None)
monkeypatch.setattr(a5_module, "is_visible", lambda *a, **k: False)
monkeypatch.setattr(a5_module, "wait", lambda *a, **k: None)
def test_open_wp_fails_fast_when_wp_never_clickable(monkeypatch):
_patch_screen(monkeypatch)
pather = _FakePather()
char = _FakeChar()
subject = A5(pather, char)
result = subject.open_wp(Location.A5_TOWN_START, quick=False)
assert result is False
# Direct attempt + at most the three NPC anchors — never an unbounded sweep.
# (Old code added a 6-step directed sweep that re-traversed each step.)
assert pather.traverse_calls <= 7
def test_open_wp_quick_mode_skips_anchors(monkeypatch):
_patch_screen(monkeypatch)
pather = _FakePather()
char = _FakeChar()
subject = A5(pather, char)
result = subject.open_wp(Location.A5_TOWN_START, quick=True)
assert result is False
# quick mode: only the single direct node path is attempted, no anchors.
assert pather.traverse_calls == 1
def test_open_wp_honors_time_budget(monkeypatch):
_patch_screen(monkeypatch)
# Force the budget negative so the deadline is always in the past (time.time()
# resolution on Windows is ~15 ms, so 0.0 stays non-expired within one tick).
monkeypatch.setattr(A5, "_OPEN_WP_BUDGET_S", -1.0)
pather = _FakePather()
char = _FakeChar()
subject = A5(pather, char)
result = subject.open_wp(Location.A5_TOWN_START, quick=False)
assert result is False
# Only the direct traverse runs; the deadline check stops the anchor loop.
assert pather.traverse_calls == 1