fix(baal_xp): restore the 09-08 join hardening lost in the 09-09 branch switch

The stash taken before switching to main (stash@{1}) carried a re-verified
_try_join - row click retried up to 3x with a re-OCR of the row when the list
scrolled, JOIN GAME press retried up to 3x when the lobby swallowed it, and a
join_game() default of 900s - but the switch was stashed, never re-applied, and
the branch tip kept the single-click version from 09-02. A single missed click
(or a list that scrolled between scan and click) then failed the whole join,
which is exactly the 'no matching game found' the cycle kept hitting.

Also add the finally: start_detecting_window() the 09-08 version had dropped:
join_game() stops the window-detection thread at the top, and every return
path (success, failure, exception) has to re-arm it or screen tracking stays
dead for the rest of the cycle.
This commit is contained in:
alexpolo1
2026-09-14 18:35:28 +02:00
parent 3a12c163ad
commit 98f9b7302e
+51 -16
View File
@@ -408,35 +408,67 @@ def _try_join(cand: dict) -> bool | None:
True = joined
False = that game was full or gone, try the next candidate
None = we are no longer in a lobby state where retrying makes sense
"""
Every click is re-verified against the screen: a single click missed by a
frame (window focus, a covered button, a list that scrolled) used to end
the whole join attempt, and the caller then reported "no matching game"
after a few seconds of a perfectly good lobby. Re-click the row when the
JOIN GAME button does not appear, re-OCR the row when the name no longer
matches (the list scrolled), and retry the JOIN press when the first one
is swallowed. Only give up on a candidate after the retries are spent."""
Logger.info(f"game_browser: joining '{cand['name']}' "
f"({cand['players'] if cand['players'] is not None else '?'} players, row {cand['row']})")
_click((LIST_X + LIST_W // 2, cand["y_center"]), "game row")
wait(0.3, 0.5)
if not is_visible(ScreenObjects.LobbyJoinBtn):
# Usually a leftover error dialog sitting on top of the lobby. Clear it
# and let the caller try the next candidate rather than aborting.
for click_attempt in range(3):
_click((LIST_X + LIST_W // 2, cand["y_center"]), "game row")
wait(0.3, 0.5)
if is_visible(ScreenObjects.LobbyJoinBtn):
break
if is_visible(ScreenObjects.ServerError):
# Usually a leftover error dialog sitting on top of the lobby. Clear
# it and let the caller try the next candidate rather than aborting.
Logger.warning("game_browser: error dialog over the lobby - dismissing")
error_screens.handle_error()
return False
if not _in_lobby():
Logger.error("game_browser: left the lobby while selecting a row")
return None
# The list may have scrolled between the scan and now: re-read the row
# we are actually pointing at before burning another click on it.
img = grab()
actual = _ocr_roi(img, [LIST_X, cand["y_center"] - 9, LIST_W, ROW_H])
if actual and actual != cand["name"]:
Logger.debug(f"game_browser: row now reads '{actual}' (was '{cand['name']}') - list moved, re-scanning")
return False
Logger.debug(f"game_browser: JOIN GAME button not up after row click {click_attempt + 1}/3 - re-clicking")
wait(0.5, 0.8)
if not is_visible(ScreenObjects.LobbyJoinBtn):
if _in_lobby():
Logger.warning("game_browser: JOIN GAME button not visible but still in the lobby - trying the next")
return False
Logger.error("game_browser: JOIN GAME button not visible after selecting a row")
return None
_click(JOIN_BTN, "JOIN GAME")
if _wait_for_join():
return True
if is_visible(ScreenObjects.LobbyJoinBtn):
Logger.info(f"game_browser: '{cand['name']}' was full or gone - trying the next candidate")
# The lobby swallows input for a moment after a bounced join.
wait(1.0, 1.5)
return False
return None
# The first JOIN press is the one that gets swallowed (lobby input lag right
# after a row click). Retry it before calling the candidate a failure.
for press in range(3):
_click(JOIN_BTN, "JOIN GAME")
if _wait_for_join():
return True
if is_visible(ScreenObjects.LobbyJoinBtn):
Logger.info(f"game_browser: '{cand['name']}' was full or gone - trying the next candidate")
# The lobby swallows input for a moment after a bounced join.
wait(1.0, 1.5)
return False
if not _in_lobby():
Logger.error("game_browser: no longer in the lobby after a JOIN GAME press")
return None
Logger.debug("game_browser: JOIN GAME press produced no state change - retrying")
wait(0.5, 0.8)
return False
def join_game(name_filter: str = "", max_wait_s: float = 60.0,
def join_game(name_filter: str = "", max_wait_s: float = 900.0,
difficulty: str = "normal", min_players: int = 3,
max_players: int = 7) -> bool:
"""
@@ -524,6 +556,9 @@ def join_game(name_filter: str = "", max_wait_s: float = 60.0,
Logger.error(f"game_browser: no matching game found within {max_wait_s}s")
return False
finally:
# Re-arm the window-detection thread on every exit path (success,
# failure, or an exception mid-join) - leaving it stopped strands the
# bot's screen tracking for the rest of the cycle.
start_detecting_window()