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:
alexpolo1
2026-06-24 15:58:13 +02:00
co-authored by Claude Opus 4.8
parent 59b851b107
commit 17bcb95c0a
3 changed files with 63 additions and 37 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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