From 853d3984217bf1e305847581451cea7a7aead493 Mon Sep 17 00:00:00 2001 From: alexpolo1 Date: Fri, 4 Sep 2026 13:26:52 +0200 Subject: [PATCH] fix(recovery): close panels before town detection; save+exit after 5 failed detects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Left standing at the stash with the stash and inventory panels open, the bot could not detect anything: the panels cover almost the whole screen, so the best town marker scored 0.49 against a 0.68 threshold. It then restarted its own process — which leaves the game exactly as it was, so the next attempt failed identically, burning the restart budget without ever touching the cause. Two changes: - Close panels before every town-marker search (on_init, wait_for_town_spawn, detect_current_act). open_wp already did this and says why; the marker searches did not. An open panel also hides the InGame indicator, which is what stopped on_init's existing save+exit recovery from firing at all — is_visible(InGame) came back False while the character was very much in a game. - Escalate to save+exit after _MAX_DETECT_FAILURES (5) consecutive failures, regardless of the in-game check, since by then that check is not to be trusted and only a fresh game guarantees a known spawn. The counter is persisted to log/.detect_fail_count the same way the restart count is, because restart_or_exit respawns the process and an in-memory counter would reset on every failure and never reach the threshold. It clears on any successful detection. Co-Authored-By: Claude Opus 5 --- src/bot.py | 56 ++++++++++++++++++++++++++++++++++++++-- src/town/town_manager.py | 12 +++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/bot.py b/src/bot.py index a1e4c43..2cf19b4 100644 --- a/src/bot.py +++ b/src/bot.py @@ -346,6 +346,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") @@ -365,6 +369,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.""" @@ -611,17 +646,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())}.") diff --git a/src/town/town_manager.py b/src/town/town_manager.py index 2e489f7..2f703cf 100644 --- a/src/town/town_manager.py +++ b/src/town/town_manager.py @@ -81,6 +81,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, @@ -130,6 +135,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: