fix(recovery): close panels before town detection; save+exit after 5 failed detects

Ported from an unpushed local main commit (853d398) that the PR #41 merge could not
include: the merge was built on origin/main, where these commits never landed.
Code only - the CLAUDE.md write-up is skipped, its bug numbers were renumbered
by the merge.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
alexpolo1
2026-09-16 15:26:51 +02:00
co-authored by Claude Opus 5
parent 1367198680
commit d73ac0403f
2 changed files with 66 additions and 2 deletions
+54 -2
View File
@@ -366,6 +366,10 @@ class Bot:
Logger.info("Shut down botty")
safe_exit()
# Consecutive on_init detection failures before we stop trusting the in-game check
# and force a save+exit. Restarting the bot process leaves the game exactly as it
# was, so without this the same failure repeats until the restart budget is gone.
_MAX_DETECT_FAILURES = 5
_MAX_CONSECUTIVE_RESTARTS = 5
_RESTART_COUNT_FILE = os.path.join("log", ".restart_count")
@@ -385,6 +389,37 @@ class Bot:
pass
return n
_DETECT_FAIL_FILE = os.path.join("log", ".detect_fail_count")
@classmethod
def _bump_detect_failures(cls) -> int:
"""Count consecutive on_init detection failures ACROSS process restarts.
restart_or_exit respawns the process, so an in-memory counter would reset to
zero on every failure and never reach the threshold.
"""
try:
with open(cls._DETECT_FAIL_FILE) as f:
n = int(f.read().strip() or 0)
except Exception:
n = 0
n += 1
try:
os.makedirs(os.path.dirname(cls._DETECT_FAIL_FILE), exist_ok=True)
with open(cls._DETECT_FAIL_FILE, "w") as f:
f.write(str(n))
except Exception:
pass
return n
@classmethod
def _reset_detect_failures(cls):
try:
if os.path.exists(cls._DETECT_FAIL_FILE):
os.remove(cls._DETECT_FAIL_FILE)
except Exception:
pass
@classmethod
def _reset_restart_count(cls):
"""Called when a game completes — the restart chain has recovered."""
@@ -673,17 +708,34 @@ class Bot:
"select_character": main_menu.MAIN_MENU_MARKERS,
"start_from_town": town_manager.TOWN_MARKERS,
})
# An open stash/inventory panel covers almost the whole screen: no town marker
# can match through it, and it hides the InGame indicator the recovery below
# depends on. Measured 2026-09-04 with the char parked at the stash — detection
# scored 0.49, the in-game check came back False, and the bot restarted its own
# process instead of recovering. Restarting changes nothing about the game, so
# it failed again identically. Close panels before deciding anything.
common.close()
if (match := template_finder.search_and_wait(list(transition_to_screens.keys()), best_match=True)).valid:
self._reset_detect_failures()
self.trigger_or_stop(transition_to_screens[match.name])
else:
detect_failures = self._bump_detect_failures()
# Stranded mid-town: town markers are position-dependent, so a char left
# standing away from the spawn shows NO known marker. If we're in-game,
# save+exit to character select and re-detect instead of giving up.
if is_visible(ScreenObjects.InGame):
Logger.warning("on_init: in-game but no known marker visible — save+exit to recover")
#
# Past _MAX_DETECT_FAILURES, save+exit unconditionally: at that point the
# in-game check itself is not to be trusted, and a fresh game is the only
# thing that guarantees a known spawn position.
forced = detect_failures >= self._MAX_DETECT_FAILURES
if is_visible(ScreenObjects.InGame) or forced:
why = (f"{detect_failures} consecutive detection failures"
if forced else "in-game but no known marker visible")
Logger.warning(f"on_init: {why} — save+exit to recover")
view.save_and_exit()
wait(2, 3)
if (match := template_finder.search_and_wait(list(transition_to_screens.keys()), best_match=True, timeout=20)).valid:
self._reset_detect_failures()
self.trigger_or_stop(transition_to_screens[match.name])
return
self.restart_or_exit(f"Failed to detect {list(transition_to_screens.keys())}.")
+12
View File
@@ -90,6 +90,11 @@ class TownManager:
:param timeout: Optional float value for time out in seconds, defaults to None
:return: Location of the town (e.g. Location.A4_TOWN_START) or None if nothing was found within timeout time
"""
# An open stash/inventory panel covers almost the whole screen, leaving only a
# narrow strip of world visible — no town marker can match through it. Measured
# 2026-09-04: the char stood at the stash with both panels open, this scored
# best A5_TOWN_1 0.49 against a 0.68 threshold, failed, and restarted the bot.
common.close()
template_match = template_finder.search_and_wait_stable(
TOWN_MARKERS,
best_match=True,
@@ -142,6 +147,13 @@ class TownManager:
if locations are assumed instead of verified.
"""
import time
# An open stash/inventory panel covers almost the whole screen, leaving only a
# narrow strip of world visible — no town marker can match through it. Measured
# 2026-09-04: the char stood at the stash with both panels open, the marker
# search scored best A5_TOWN_1 0.49 against a 0.68 threshold, detection failed
# and the bot restarted itself. open_wp already closes panels first for exactly
# this reason; the marker searches did not.
common.close()
deadline = time.time() + timeout
closest = None
while True: