fix(restart): a self-restarted bot resumes playing instead of waiting for F11
Both self-restart paths (Bot.restart_or_exit, GameController "could not recover") spawn a replacement process that waited for the resume key like a manual launch. With nobody at the keyboard every self-recovery became a permanent stall - the "bot is stuck all the time" report on 2026-09-15. - Replacement processes get BOTTY_AUTOSTART=1; main.py starts the bot when it is set and D2R is running. A manual launch still waits for the key. - The controller path now shares Bot's restart cap (reset on reaching town), so an unrecoverable screen stops after 5 restarts instead of looping. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b0d8fdf028
commit
2f79acad8d
+5
-1
@@ -348,7 +348,11 @@ class Bot:
|
||||
Logger.info(f"Restarting bot — game kept running (attempt {n}/{self._MAX_CONSECUTIVE_RESTARTS}, {delay}s backoff)")
|
||||
time.sleep(delay)
|
||||
import subprocess
|
||||
subprocess.Popen([sys.executable, os.path.abspath(sys.argv[0])])
|
||||
# The replacement must start playing on its own. Without this it sat at
|
||||
# "press the resume key" with nobody there, so every self-recovery
|
||||
# became a permanent stall (2026-09-15: idle from 17:50 until noticed).
|
||||
subprocess.Popen([sys.executable, os.path.abspath(sys.argv[0])],
|
||||
env={**os.environ, "BOTTY_AUTOSTART": "1"})
|
||||
os._exit(0)
|
||||
else:
|
||||
Logger.info("Shut down botty")
|
||||
|
||||
+10
-1
@@ -156,8 +156,17 @@ class GameController:
|
||||
if max_game_length_reached and not self.game_stats.get_failure_reason():
|
||||
self.game_stats.set_failure_reason("Max game length reached (stuck)")
|
||||
self.game_stats.log_end_game(failed=max_game_length_reached)
|
||||
# Same bound as Bot.restart_or_exit (shared counter file, reset when a
|
||||
# game completes): the replacement auto-starts, so an unrecoverable
|
||||
# screen must not restart forever.
|
||||
n = Bot._bump_restart_count()
|
||||
if n > Bot._MAX_CONSECUTIVE_RESTARTS:
|
||||
Logger.error(f"Restarted {n - 1}x without completing a game — stopping instead of looping.")
|
||||
Bot._reset_restart_count()
|
||||
safe_exit(1)
|
||||
import subprocess, sys, os as _os
|
||||
subprocess.Popen([sys.executable, _os.path.abspath(sys.argv[0])])
|
||||
subprocess.Popen([sys.executable, _os.path.abspath(sys.argv[0])],
|
||||
env={**_os.environ, "BOTTY_AUTOSTART": "1"})
|
||||
_os._exit(0)
|
||||
else:
|
||||
Logger.error("Could not recover from a max game length violation. Quitting botty.")
|
||||
|
||||
+11
@@ -244,6 +244,17 @@ def main():
|
||||
keyboard.add_hotkey(Config().advanced_options['resume_key'], lambda: start_or_pause_bot(controllers))
|
||||
keyboard.add_hotkey(Config().advanced_options["exit_key"], lambda: on_exit(controllers))
|
||||
|
||||
# A self-restart (Bot.restart_or_exit) sets BOTTY_AUTOSTART: resume playing
|
||||
# instead of waiting for a resume key nobody is there to press. A normal manual
|
||||
# launch still waits for the key. restart_or_exit's counter bounds the loop.
|
||||
if os.environ.get("BOTTY_AUTOSTART") == "1":
|
||||
from utils.restart import process_exists
|
||||
if os.name != "nt" or process_exists("D2R.exe"):
|
||||
Logger.info("Auto-starting after a bot self-restart (no resume key needed)")
|
||||
start_or_pause_bot(controllers)
|
||||
else:
|
||||
Logger.warning("Self-restart found D2R not running — launch D2R, then press the resume key")
|
||||
|
||||
# Hermes Agent control socket — TCP server on localhost:18899
|
||||
# Accepts commands: start, pause, stop, status
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""A bot that restarts itself must resume playing on its own.
|
||||
|
||||
Both self-restart paths spawn a replacement process. That process used to wait for
|
||||
the resume key like a manual launch, so every self-recovery became a permanent stall
|
||||
with nobody there to press F11 (2026-09-15).
|
||||
"""
|
||||
import inspect
|
||||
|
||||
|
||||
def test_bot_restart_passes_autostart_to_replacement():
|
||||
from bot import Bot
|
||||
|
||||
src = inspect.getsource(Bot.restart_or_exit)
|
||||
assert '"BOTTY_AUTOSTART": "1"' in src
|
||||
|
||||
|
||||
def test_controller_restart_passes_autostart_and_is_bounded():
|
||||
from game_controller import GameController
|
||||
|
||||
src = inspect.getsource(GameController)
|
||||
assert '"BOTTY_AUTOSTART": "1"' in src
|
||||
popen = src.index('"BOTTY_AUTOSTART": "1"')
|
||||
bump = src.rfind("Bot._bump_restart_count()", 0, popen)
|
||||
assert bump != -1, "controller restart must share Bot's restart cap before spawning"
|
||||
|
||||
|
||||
def test_main_autostarts_only_when_flagged():
|
||||
import main
|
||||
|
||||
src = inspect.getsource(main.main)
|
||||
flag = src.index('os.environ.get("BOTTY_AUTOSTART") == "1"')
|
||||
assert "start_or_pause_bot(controllers)" in src[flag:flag + 600]
|
||||
Reference in New Issue
Block a user