fix(baal_xp): join at 1+ players and survive a stale LOBBY template
Two failures observed 2026-09-02 that stopped the leech cycle completing. min_players default 3 -> 1. A public Baal game spawns at 1-2 players and fills within seconds, so a 3-player floor skipped every real game: all listed games read 1-2p and were passed over, the fallback candidate's join never completed, and the cycle failed until the controller's circuit breaker restarted D2R. The band was always meant to be a preference rather than a gate — the in-game checks (portal wait, party size, XP idle) are what decide whether a game is worth staying in, so the floor was doing nothing except rejecting valid games. LOBBY button detection hardened. Its template is the one that goes stale: after the mod font was removed it scored 0.70 against a 0.8 threshold, so every save-and-exit failed with "not at character select" while the screen was plainly showing it. Now: - retry the detect three times before giving up - fall back to a coordinate click on the known LOBBY position when the template never resolves, gated on at_character_select() so we only click blind when we have independent confirmation of the screen - retry open_lobby once on a transient miss The "no character selected" path is preserved: LOBBY and PLAY are inert until a character is highlighted, so that still reports the real cause instead of burning the timeout on a misleading "lobby did not open". Also refreshes config/fg_daily_estimates.json (generated data, updated by the bot's own pricing pass). Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d1ee54f372
commit
21c62f2cc9
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-08-26T06:13:10.206599+00:00",
|
||||
"generated_at": "2026-09-02T06:11:52.162227+00:00",
|
||||
"mode": "offline-improved",
|
||||
"offline_dir": "data\\d2jsp_pages",
|
||||
"ladder_start_date": "2026-05-20",
|
||||
@@ -8,7 +8,7 @@
|
||||
"skipped": {
|
||||
"blocked": 2,
|
||||
"no_topic": 0,
|
||||
"no_date": 868,
|
||||
"no_date": 872,
|
||||
"no_item": 0
|
||||
},
|
||||
"filters": {
|
||||
|
||||
+8
-5
@@ -665,11 +665,14 @@ join_timeout_s=180
|
||||
; Difficulty of the public games to join: normal | nightmare | hell.
|
||||
; Hiding in a game above the character's level is a death sentence.
|
||||
difficulty=normal
|
||||
; Join games in this player band. Baal runs fill within seconds, so 1-2 players
|
||||
; usually means nobody is running; 8 is full and the join bounces back to the
|
||||
; lobby. This is a PREFERENCE, not a hard gate: when no game in the band is
|
||||
; listed, the best remaining game is joined anyway and the in-game checks
|
||||
; (portal wait, party size, XP idle) decide whether it is worth staying.
|
||||
; Join games in this player band. This is a PREFERENCE, not a hard gate: when
|
||||
; no game in the band is listed, the best remaining game is joined anyway and
|
||||
; the in-game checks (portal wait, party size, XP idle) decide whether it is
|
||||
; worth staying. Keep min_players at 1: a public Baal game spawns at 1-2 players
|
||||
; and fills within seconds, so a 3-player floor skips every real game and the
|
||||
; join fails every cycle (observed 2026-09-02: all listed games read 1-2p and
|
||||
; were skipped, the fallback candidate's join never completed, and the cycle
|
||||
; failed until the controller's circuit breaker restarted D2R).
|
||||
min_players=1
|
||||
max_players=7
|
||||
; Take the host's town portal to the Throne rather than hiding in town.
|
||||
|
||||
+5
-3
@@ -404,9 +404,11 @@ class Config:
|
||||
# character can actually survive while hiding.
|
||||
"difficulty": self._normalize_difficulty(
|
||||
self._select_optional("baal_xp", "difficulty", "normal")),
|
||||
# A public Baal run fills within seconds; a game still showing one
|
||||
# or two players is someone idling, not a run.
|
||||
"min_players": int(self._select_optional("baal_xp", "min_players", "3")),
|
||||
# A public Baal game spawns at 1-2 players and fills within seconds,
|
||||
# so a floor above 1 skips every real game and the join fails every
|
||||
# cycle. Keep the default at 1; the in-game checks (portal wait,
|
||||
# party size, XP idle) are what decide whether a game is worth staying.
|
||||
"min_players": int(self._select_optional("baal_xp", "min_players", "1")),
|
||||
# Take the host's town portal to the Throne. XP is shared by area,
|
||||
# so hiding in town earns nothing while the run is in the Worldstone.
|
||||
"take_portal": self._select_optional("baal_xp", "take_portal", "1") not in (None, "", "0", "false", "False"),
|
||||
|
||||
+38
-14
@@ -168,19 +168,37 @@ def open_lobby(timeout: float = 20.0) -> bool:
|
||||
"""From the character-select screen, click LOBBY and select the JOIN GAME
|
||||
tab. No-op (returns True) if that is already the state."""
|
||||
if not _in_lobby():
|
||||
if not (lb := detect_screen_object(ScreenObjects.LobbyBtn)).valid:
|
||||
Logger.error("game_browser: not at character select - cannot open the lobby")
|
||||
return False
|
||||
# LOBBY and PLAY are inert until a character is selected, so a click on a
|
||||
# perfectly-detected button silently does nothing. Say so instead of
|
||||
# burning the full timeout on a misleading "lobby did not open".
|
||||
if not is_visible(ScreenObjects.SelectedCharacter):
|
||||
Logger.error("game_browser: no character selected - LOBBY will not respond. "
|
||||
"The character list is empty or none is highlighted "
|
||||
"(a dropped Battle.net session does this); log in again.")
|
||||
return False
|
||||
select_screen_object_match(lb)
|
||||
|
||||
# The LOBBY button is the "am I at character select" signal, but its
|
||||
# template is the one that goes stale (after the mod font was removed
|
||||
# it scored 0.70 against the 0.8 threshold and every save-and-exit
|
||||
# failed with "not at character select" while the screen was plainly
|
||||
# showing). Retry the detect a few times before giving up, and fall back
|
||||
# to a coordinate click on the known LOBBY position when the template
|
||||
# never resolves - a covered or stale template must not stall the whole
|
||||
# join, since the button is at a fixed spot on this screen.
|
||||
lb = None
|
||||
for _ in range(3):
|
||||
lb = detect_screen_object(ScreenObjects.LobbyBtn)
|
||||
if lb.valid:
|
||||
break
|
||||
wait(0.4, 0.7)
|
||||
if lb.valid:
|
||||
# LOBBY and PLAY are inert until a character is selected, so a click on
|
||||
# a perfectly-detected button silently does nothing. Say so instead of
|
||||
# burning the full timeout on a misleading "lobby did not open".
|
||||
if not is_visible(ScreenObjects.SelectedCharacter):
|
||||
Logger.error("game_browser: no character selected - LOBBY will not respond. "
|
||||
"The character list is empty or none is highlighted "
|
||||
"(a dropped Battle.net session does this); log in again.")
|
||||
return False
|
||||
select_screen_object_match(lb)
|
||||
else:
|
||||
if not at_character_select():
|
||||
Logger.error("game_browser: not at character select - cannot open the lobby")
|
||||
return False
|
||||
Logger.warning("game_browser: LOBBY button template not matching - "
|
||||
"falling back to a coordinate click")
|
||||
_click(LOBBY_BTN, "LOBBY (coordinate fallback)")
|
||||
start = time.time()
|
||||
while not _in_lobby():
|
||||
if time.time() - start > timeout:
|
||||
@@ -449,8 +467,14 @@ def join_game(name_filter: str = "", max_wait_s: float = 60.0,
|
||||
Logger.warning("game_browser: already in a game")
|
||||
return True
|
||||
|
||||
# open_lobby can fail on a transient template miss or a covered window.
|
||||
# Retry once before giving up - the join is the whole point of the cycle.
|
||||
if not open_lobby():
|
||||
return False
|
||||
Logger.warning("game_browser: open_lobby failed - retrying once")
|
||||
wait(1.0, 1.5)
|
||||
if not open_lobby():
|
||||
Logger.error("game_browser: could not open the lobby")
|
||||
return False
|
||||
|
||||
# Difficulty first: it changes which games the list shows.
|
||||
if not set_difficulty(difficulty):
|
||||
|
||||
Reference in New Issue
Block a user