fix: make the Diablo (Chaos Sanctuary) run complete end-to-end
The run_diablo route was failing every game. Diagnosed and fixed live — a full run now clears all three seals (Vizier, De Seis, Infector) and loots. Pentagram navigation (was the #1 abort: "battle_failed", char stranded in CS trash, pentagram never detected): - _loop_pentagram now falls back to active node-602 navigation when the blind fixed-path teleport loop fails to surface the pentagram. Node 602 searches the PENT templates directly and teleports toward them with the pather's auto- recovery sweep — the same robust approach _cs_pentagram already uses. Applied in both diablo.py and vizier.py. - Combined with the lowered _PENT_THRESHOLD (0.50), the pentagram now resolves: live reads were 57-96% where the old 0.83 threshold rejected them. Seal layout check (next abort after the pentagram fix, at the Vizier seal): - Added per-seal score logging (LC primary/confirm). This revealed the real cause is character-positioning variance, NOT template drift: the true layout reads 84-88% and the other 42-57% (clean separation) when well-positioned, but from a bad camera angle BOTH read ~55-66% and the check is ambiguous. - So the fix is more re-approach attempts (max_attempts 2 -> 3), not lower thresholds — lowering a disambiguation threshold risks picking the WRONG seal from a bad-position read. - Normalized seal-A threshold_confirmation 0.85 -> 0.80 (every other seal is 0.80; safe given the 84-88% vs 42-57% separation). Crash fix (game_recovery.py): go_to_hero_selection had been dedented to module level while its body kept method indentation, so it fell out of the GameRecovery class. Every post-chicken/death/failed-game recovery threw AttributeError: 'GameRecovery' object has no attribute 'go_to_hero_selection' and killed the run_bot thread. Re-indented into the class. Verified live: the bot now recovers from failed games and auto-starts the next one. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
59b851b107
commit
17bcb95c0a
@@ -12,7 +12,7 @@ class GameRecovery:
|
||||
def __init__(self, death_manager: DeathManager):
|
||||
self._death_manager = death_manager
|
||||
|
||||
def go_to_hero_selection(self):
|
||||
def go_to_hero_selection(self):
|
||||
set_d2r_always_on_top()
|
||||
wait(1, 1.5)
|
||||
# clean up key presses that might be pressed in the run_thread
|
||||
|
||||
+47
-23
@@ -147,11 +147,17 @@ class Diablo:
|
||||
Logger.info(f"_verify_in_rof: confirmed in River of Flame ({rof_check.name} @ {rof_check.score*100:.1f}%)")
|
||||
return True
|
||||
|
||||
# All 8 pentagram templates — templates 0-2/TP are stale vs current D2R rendering;
|
||||
# template 4 consistently scores highest (0.53–0.66). Use all for maximum coverage.
|
||||
_PENT_TEMPLATES = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2",
|
||||
"DIA_NEW_PENT_3", "DIA_NEW_PENT_4", "DIA_NEW_PENT_5", "DIA_NEW_PENT_6"]
|
||||
_PENT_THRESHOLD = 0.50
|
||||
|
||||
def _verify_at_pentagram(self) -> bool:
|
||||
"""Check that we are at the Pentagram area after teleporting."""
|
||||
pent_check = template_finder.search_and_wait_stable(
|
||||
["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"],
|
||||
threshold=0.8, timeout=3.0, confirmations=2, suppress_debug=True
|
||||
self._PENT_TEMPLATES,
|
||||
threshold=self._PENT_THRESHOLD, timeout=3.0, confirmations=2, suppress_debug=True
|
||||
)
|
||||
if not pent_check.valid:
|
||||
Logger.warning("_verify_at_pentagram: no Pentagram marker found")
|
||||
@@ -272,11 +278,23 @@ class Diablo:
|
||||
# LOOP TO PENTAGRAM
|
||||
def _loop_pentagram(self, path) -> bool:
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
start_time = time.time()
|
||||
while not found and time.time() - start_time < 15:
|
||||
found = template_finder.search_and_wait(templates, threshold=0.83, timeout=0.1, suppress_debug=True).valid
|
||||
found = template_finder.search_and_wait(self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.1, suppress_debug=True).valid
|
||||
if not found: self._pather.traverse_nodes_fixed(path, self._char)
|
||||
if not found:
|
||||
# The blind fixed-path loop teleports along a static route while watching
|
||||
# for the Pentagram; it fails whenever the entrance-hall clear ends in a
|
||||
# variable spot or trash blocks the teleport (the #1 run_diablo abort —
|
||||
# "battle_failed", char stranded among CS trash, Pentagram never on screen).
|
||||
# Fall back to active node-based navigation: node 602 searches the PENT
|
||||
# templates directly and teleports toward them with the pather's auto-
|
||||
# recovery sweep — the same robust approach _cs_pentagram already uses.
|
||||
Logger.warning("_loop_pentagram: fixed-path loop failed — falling back to node 602 navigation")
|
||||
if self._pather.traverse_nodes([602], self._char, threshold=0.80):
|
||||
found = template_finder.search_and_wait(
|
||||
self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.5, suppress_debug=True
|
||||
).valid
|
||||
if not found:
|
||||
if Config().general["info_screenshots"]: safe_imwrite(f"./log/screenshots/info/info_failed_loop_pentagram_" + path + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
|
||||
return False
|
||||
@@ -347,10 +365,9 @@ class Diablo:
|
||||
self._pather.traverse_nodes_fixed("diablo_wp_pentagram_2", self._char)
|
||||
Logger.debug("ROF: Teleporting directly to PENTAGRAM")
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
start_time = time.time()
|
||||
while not found and time.time() - start_time < 10:
|
||||
found = template_finder.search_and_wait(templates, threshold=0.8, timeout=0.1, suppress_debug=True).valid
|
||||
found = template_finder.search_and_wait(self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.1, suppress_debug=True).valid
|
||||
if not found:
|
||||
self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
|
||||
if not found:
|
||||
@@ -392,15 +409,6 @@ class Diablo:
|
||||
if not self._entrance_hall(): return False
|
||||
Logger.debug("CS Trash: looping to PENTAGRAM")
|
||||
if not self._loop_pentagram("diablo_wp_pentagram_loop"): return False
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
start_time = time.time()
|
||||
while not found and time.time() - start_time < 15:
|
||||
found = template_finder.search_and_wait(templates, threshold=0.83, timeout=0.1, suppress_debug=True).valid
|
||||
if not found: self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
|
||||
if not found:
|
||||
if Config().general["info_screenshots"]: safe_imwrite(f"./log/screenshots/info/info_failed_loop_pentagram_diablo_wp_pentagram_loop_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
|
||||
return False
|
||||
# Final verification: confirm we're at the Pentagram
|
||||
if not self._verify_at_pentagram():
|
||||
Logger.warning("_river_of_flames_trash: Pentagram verification failed after loop")
|
||||
@@ -497,7 +505,11 @@ class Diablo:
|
||||
params_seal1 = seal_layout1, [614], [615], [611], "dia_a1l_home", "dia_a1l_home_loop", [602], ["DIA_A1L2_14_OPEN"], ["DIA_A1L2_14_CLOSED", "DIA_A1L2_14_CLOSED_DARK", "DIA_A1L2_14_MOUSEOVER"], ["DIA_A1L2_5_OPEN"], ["DIA_A1L2_5_CLOSED","DIA_A1L2_5_MOUSEOVER"]
|
||||
params_seal2 = seal_layout2, [625], [626], [622], "dia_a2y_home", "dia_a2y_home_loop", [602], ["DIA_A2Y4_29_OPEN"], ["DIA_A2Y4_29_CLOSED", "DIA_A2Y4_29_MOUSEOVER"], ["DIA_A2Y4_36_OPEN"], ["DIA_A2Y4_36_CLOSED", "DIA_A2Y4_36B_CLOSED", "DIA_A2Y4_36_MOUSEOVER"]
|
||||
threshold_primary=0.8
|
||||
threshold_confirmation=0.85
|
||||
# Normalized from 0.85 to 0.80 to match every other seal threshold.
|
||||
# Live scores show clean separation (true layout 84-88%, other 42-57%),
|
||||
# so 0.80 carries no false-positive risk and better catches a genuine
|
||||
# A1-L that reads ~80-85% from a slightly-off position. (log 2026-06-24)
|
||||
threshold_confirmation=0.80
|
||||
threshold_confirmation2=0.8
|
||||
confirmation_node2=None
|
||||
case "B":
|
||||
@@ -523,11 +535,17 @@ class Diablo:
|
||||
return False
|
||||
|
||||
# Layout decision: 1 = seal_layout1, 2 = seal_layout2, 0 = undetermined.
|
||||
# A blind static path can drift off course (one missed teleport click derails
|
||||
# the rest), so an ambiguous/lost check gets ONE retry: loop back to the
|
||||
# pentagram, re-run the approach, check again.
|
||||
# The check is reliable WHEN well-positioned: live scores show the true layout
|
||||
# at 84-88% and the other at 42-57% (clear separation). The failure mode is
|
||||
# positioning variance — from a bad camera angle BOTH templates read low
|
||||
# (~55-66%) and the check is ambiguous. So an ambiguous/lost check loops back
|
||||
# to the pentagram and re-approaches for a fresh, hopefully better-positioned
|
||||
# read. 3 attempts: a single bad-position retry was the top run_diablo abort
|
||||
# after the Pentagram fix (log 2026-06-24); the extra approach is cheap vs
|
||||
# losing the whole run. Do NOT "fix" ambiguity by lowering thresholds — that
|
||||
# risks picking the WRONG seal from a bad-position read.
|
||||
decision = 0
|
||||
max_attempts = 2
|
||||
max_attempts = 3
|
||||
for attempt in range(max_attempts):
|
||||
if attempt > 0:
|
||||
Logger.warning(f"{sealname}: Layout_check retry {attempt + 1}/{max_attempts} - looping back to pentagram to recover position")
|
||||
@@ -541,14 +559,18 @@ class Diablo:
|
||||
Logger.warning(f"{sealname}: Layout_check calibration node not found - position lost")
|
||||
continue
|
||||
#check1 using primary templates
|
||||
if not template_finder.search_and_wait(templates_primary, threshold =threshold_primary, timeout=0.5).valid:
|
||||
primary_m = template_finder.search_and_wait(templates_primary, threshold=threshold_primary, timeout=0.5)
|
||||
Logger.info(f"{sealname}: LC primary({seal_layout2}) best={primary_m.name}@{primary_m.score*100:.1f}% (thr {threshold_primary*100:.0f}%)")
|
||||
if not primary_m.valid:
|
||||
Logger.debug(f"{seal_layout1}: Layout_check step 1/2 - templates NOT found for "f"{seal_layout2}")
|
||||
#cross-check for confirmation
|
||||
if not confirmation_node == None:
|
||||
if not self._pather.traverse_nodes(confirmation_node, self._char, threshold=calibration_threshold,):
|
||||
Logger.warning(f"{sealname}: Layout_check confirmation node not found - position lost")
|
||||
continue
|
||||
if template_finder.search_and_wait(templates_confirmation, threshold=threshold_confirmation, timeout=0.5).valid:
|
||||
conf_m = template_finder.search_and_wait(templates_confirmation, threshold=threshold_confirmation, timeout=0.5)
|
||||
Logger.info(f"{sealname}: LC confirm({seal_layout1}) best={conf_m.name}@{conf_m.score*100:.1f}% (thr {threshold_confirmation*100:.0f}%)")
|
||||
if conf_m.valid:
|
||||
Logger.info(f"{seal_layout1}: Layout_check step 2/2 - templates found for "f"{seal_layout1} - "+'\033[93m'+"all fine, proceeding with "f"{seal_layout1}"+'\033[0m')
|
||||
decision = 1
|
||||
break
|
||||
@@ -559,7 +581,9 @@ class Diablo:
|
||||
if not self._pather.traverse_nodes(confirmation_node2, self._char, threshold=calibration_threshold,):
|
||||
Logger.warning(f"{sealname}: Layout_check confirmation node not found - position lost")
|
||||
continue
|
||||
if not template_finder.search_and_wait(templates_confirmation, threshold=threshold_confirmation2, timeout=0.5).valid:
|
||||
conf2_m = template_finder.search_and_wait(templates_confirmation, threshold=threshold_confirmation2, timeout=0.5)
|
||||
Logger.info(f"{sealname}: LC confirm2({seal_layout1}) best={conf2_m.name}@{conf2_m.score*100:.1f}% (thr {threshold_confirmation2*100:.0f}%)")
|
||||
if not conf2_m.valid:
|
||||
Logger.info(f"{seal_layout2}: Layout_check step 2/2 - templates NOT found for "f"{seal_layout1} - "+'\033[96m'+"all fine, proceeding with "f"{seal_layout2}"+'\033[0m')
|
||||
decision = 2
|
||||
break
|
||||
|
||||
+15
-13
@@ -87,14 +87,26 @@ class Vizier:
|
||||
return found
|
||||
|
||||
|
||||
_PENT_TEMPLATES = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2",
|
||||
"DIA_NEW_PENT_3", "DIA_NEW_PENT_4", "DIA_NEW_PENT_5", "DIA_NEW_PENT_6"]
|
||||
_PENT_THRESHOLD = 0.50
|
||||
|
||||
# LOOP TO PENTAGRAM
|
||||
def _loop_pentagram(self, path) -> bool:
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
for _ in range(3):
|
||||
found = template_finder.search_and_wait(templates, threshold=0.83, timeout=0.3, suppress_debug=True).valid
|
||||
found = template_finder.search_and_wait(self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.3, suppress_debug=True).valid
|
||||
if not found: self._pather.traverse_nodes_fixed(path, self._char)
|
||||
else: break
|
||||
if not found:
|
||||
# Blind fixed-path loop didn't surface the Pentagram. Fall back to active
|
||||
# node-based navigation (node 602 searches the PENT templates and teleports
|
||||
# toward them with auto-recovery) instead of aborting the run.
|
||||
Logger.warning("_loop_pentagram: fixed-path loop failed — falling back to node 602 navigation")
|
||||
if self._pather.traverse_nodes([602], self._char, threshold=0.80):
|
||||
found = template_finder.search_and_wait(
|
||||
self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.5, suppress_debug=True
|
||||
).valid
|
||||
if not found:
|
||||
if Config().general["info_screenshots"]: safe_imwrite(f"./log/screenshots/info/info_failed_loop_pentagram_" + path + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
|
||||
return False
|
||||
@@ -161,10 +173,9 @@ class Vizier:
|
||||
self._pather.traverse_nodes_fixed("diablo_wp_pentagram_2", self._char)
|
||||
Logger.debug("ROF: Teleporting directly to PENTAGRAM")
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
start_time = time.time()
|
||||
while not found and time.time() - start_time < 10:
|
||||
found = template_finder.search_and_wait(templates, threshold=0.8, timeout=0.1, suppress_debug=True).valid
|
||||
found = template_finder.search_and_wait(self._PENT_TEMPLATES, threshold=self._PENT_THRESHOLD, timeout=0.1, suppress_debug=True).valid
|
||||
if not found:
|
||||
self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
|
||||
if not found:
|
||||
@@ -200,15 +211,6 @@ class Vizier:
|
||||
if not self._entrance_hall(): return False
|
||||
Logger.debug("CS Trash: looping to PENTAGRAM")
|
||||
if not self._loop_pentagram("diablo_wp_pentagram_loop"): return False
|
||||
found = False
|
||||
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
|
||||
start_time = time.time()
|
||||
while not found and time.time() - start_time < 15:
|
||||
found = template_finder.search_and_wait(templates, threshold=0.83, timeout=0.1, suppress_debug=True).valid
|
||||
if not found: self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
|
||||
if not found:
|
||||
if Config().general["info_screenshots"]: safe_imwrite(f"./log/screenshots/info/info_failed_loop_pentagram_diablo_wp_pentagram_loop_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user