fix(recovery): close panels before town detection; save+exit after 5 failed detects
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 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
477c693703
commit
853d398421
+54
-2
@@ -346,6 +346,10 @@ class Bot:
|
|||||||
Logger.info("Shut down botty")
|
Logger.info("Shut down botty")
|
||||||
safe_exit()
|
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
|
_MAX_CONSECUTIVE_RESTARTS = 5
|
||||||
_RESTART_COUNT_FILE = os.path.join("log", ".restart_count")
|
_RESTART_COUNT_FILE = os.path.join("log", ".restart_count")
|
||||||
|
|
||||||
@@ -365,6 +369,37 @@ class Bot:
|
|||||||
pass
|
pass
|
||||||
return n
|
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
|
@classmethod
|
||||||
def _reset_restart_count(cls):
|
def _reset_restart_count(cls):
|
||||||
"""Called when a game completes — the restart chain has recovered."""
|
"""Called when a game completes — the restart chain has recovered."""
|
||||||
@@ -611,17 +646,34 @@ class Bot:
|
|||||||
"select_character": main_menu.MAIN_MENU_MARKERS,
|
"select_character": main_menu.MAIN_MENU_MARKERS,
|
||||||
"start_from_town": town_manager.TOWN_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:
|
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])
|
self.trigger_or_stop(transition_to_screens[match.name])
|
||||||
else:
|
else:
|
||||||
|
detect_failures = self._bump_detect_failures()
|
||||||
# Stranded mid-town: town markers are position-dependent, so a char left
|
# 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,
|
# 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.
|
# 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()
|
view.save_and_exit()
|
||||||
wait(2, 3)
|
wait(2, 3)
|
||||||
if (match := template_finder.search_and_wait(list(transition_to_screens.keys()), best_match=True, timeout=20)).valid:
|
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])
|
self.trigger_or_stop(transition_to_screens[match.name])
|
||||||
return
|
return
|
||||||
self.restart_or_exit(f"Failed to detect {list(transition_to_screens.keys())}.")
|
self.restart_or_exit(f"Failed to detect {list(transition_to_screens.keys())}.")
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ class TownManager:
|
|||||||
:param timeout: Optional float value for time out in seconds, defaults to None
|
: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
|
: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(
|
template_match = template_finder.search_and_wait_stable(
|
||||||
TOWN_MARKERS,
|
TOWN_MARKERS,
|
||||||
best_match=True,
|
best_match=True,
|
||||||
@@ -130,6 +135,13 @@ class TownManager:
|
|||||||
if locations are assumed instead of verified.
|
if locations are assumed instead of verified.
|
||||||
"""
|
"""
|
||||||
import time
|
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
|
deadline = time.time() + timeout
|
||||||
closest = None
|
closest = None
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
Reference in New Issue
Block a user