diff --git a/src/game_recovery.py b/src/game_recovery.py index b5de881..00047d6 100644 --- a/src/game_recovery.py +++ b/src/game_recovery.py @@ -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 diff --git a/src/run/diablo.py b/src/run/diablo.py index 7c7a617..94972d2 100644 --- a/src/run/diablo.py +++ b/src/run/diablo.py @@ -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 diff --git a/src/run/vizier.py b/src/run/vizier.py index 224d658..0fcd2ba 100644 --- a/src/run/vizier.py +++ b/src/run/vizier.py @@ -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