From d0d2a549992ae5ff749b1dc97b7a3ebdee4d2aca Mon Sep 17 00:00:00 2001 From: alexpolo1 Date: Sat, 5 Sep 2026 08:17:30 +0200 Subject: [PATCH] fix(baal_xp): reset the game-length clock when the leech lands back in town The leech cycle leaves the bot's own game for a public one and comes back. The controller's max_game_length breaker counts real game time, so the clock can already be over budget the moment the cycle ends - and it would kill the game before the next cycle starts. Reset it in on_end_run for baal_xp. Also: on char-select failure in the join phases, go home before end_run (the character is stranded on the character screen otherwise), and grade the cycle by whether recovery to the own game succeeded - a cycle that left early (low HP, timer) is a success, only a broken cycle (join/leave/recovery failure) is a failure. --- src/bot.py | 23 ++++++++++++++++++----- src/game_stats.py | 10 ++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/bot.py b/src/bot.py index eaa288b..980d4a5 100644 --- a/src/bot.py +++ b/src/bot.py @@ -984,6 +984,12 @@ class Bot: return if not self._curr_loc: self._curr_loc = self._verify_town_location() + # The leech cycle ends in its own game, but the controller's + # max_game_length breaker counts real game time - and the bot's own + # game has been running since game start, so it can already be over + # budget the moment we land back in town. Reset the clock here or + # the controller kills the game before the next cycle starts. + self._game_stats.reset_game_timer() set_pause_state(True) self.trigger_or_stop("maintenance") return @@ -1905,6 +1911,7 @@ class Bot: if not self._baal_xp_select_char(): self._game_stats.set_failure_reason("baal_xp: char select failed") self._save_error_screenshot("baal_xp", "char_select_failed") + self._baal_xp_go_home() self.trigger_or_stop("end_run") return @@ -1928,6 +1935,7 @@ class Bot: if not self._baal_xp_select_char(): self._game_stats.set_failure_reason("baal_xp: char select failed") self._save_error_screenshot("baal_xp", "char_select_failed") + self._baal_xp_go_home() self.trigger_or_stop("end_run") return @@ -1990,13 +1998,18 @@ class Bot: self._save_error_screenshot("baal_xp", "leave_failed") # --- Phase 8: Recover to own game --- - self._baal_xp_go_home() + ok = self._recover_to_own_game() + if not ok: + self._game_stats.set_failure_reason("baal_xp: recovery to own game failed") + self._save_error_screenshot("baal_xp", "recovery_failed") - # Log run result - self._game_stats.log_run_finished("run_baal_xp", False, None, loot=[]) - self._record_run_result("run_baal_xp", False) + # Log run result. A completed cycle is a success even when it left + # early (low HP, run over) - the XP was collected; only a broken + # cycle (join/leave/recovery failure) is a failure. + self._game_stats.log_run_finished("run_baal_xp", not ok, None, loot=[]) + self._record_run_result("run_baal_xp", not ok) self._game_stats.log_exp() - self._baal_xp_rearm(ok=True) + self._baal_xp_rearm(ok=ok) self.trigger_or_stop("end_run") except Exception as e: import traceback diff --git a/src/game_stats.py b/src/game_stats.py index 408325e..8e62e5f 100644 --- a/src/game_stats.py +++ b/src/game_stats.py @@ -371,6 +371,16 @@ class GameStats: else: return time.time() - self._timer + def reset_game_timer(self): + """Restart the game-length clock from now. + + The leech cycle leaves the bot's own game for a public one and comes + back - the time spent in the public game is not stuck time in the + bot's game, and the controller's max_game_length breaker would + otherwise kill the game the moment the bot lands back in town.""" + self._timer = time.time() + self._paused = False + def get_consecutive_runs_failed(self): return self._consecutive_runs_failed