diff --git a/assets/templates/skills_spellbook/blessed_hammer.png b/assets/templates/skills_spellbook/blessed_hammer.png new file mode 100644 index 0000000..8071848 Binary files /dev/null and b/assets/templates/skills_spellbook/blessed_hammer.png differ diff --git a/assets/templates/skills_spellbook/cleansing.png b/assets/templates/skills_spellbook/cleansing.png new file mode 100644 index 0000000..45130b7 Binary files /dev/null and b/assets/templates/skills_spellbook/cleansing.png differ diff --git a/assets/templates/skills_spellbook/concentration.png b/assets/templates/skills_spellbook/concentration.png new file mode 100644 index 0000000..9880503 Binary files /dev/null and b/assets/templates/skills_spellbook/concentration.png differ diff --git a/assets/templates/skills_spellbook/conviction.png b/assets/templates/skills_spellbook/conviction.png new file mode 100644 index 0000000..8bc13b0 Binary files /dev/null and b/assets/templates/skills_spellbook/conviction.png differ diff --git a/assets/templates/skills_spellbook/defiance.png b/assets/templates/skills_spellbook/defiance.png new file mode 100644 index 0000000..3d8a0c5 Binary files /dev/null and b/assets/templates/skills_spellbook/defiance.png differ diff --git a/assets/templates/skills_spellbook/fist_of_the_heavens.png b/assets/templates/skills_spellbook/fist_of_the_heavens.png new file mode 100644 index 0000000..1ecf5d0 Binary files /dev/null and b/assets/templates/skills_spellbook/fist_of_the_heavens.png differ diff --git a/assets/templates/skills_spellbook/holy_shield.png b/assets/templates/skills_spellbook/holy_shield.png new file mode 100644 index 0000000..e47a8b6 Binary files /dev/null and b/assets/templates/skills_spellbook/holy_shield.png differ diff --git a/assets/templates/skills_spellbook/might.png b/assets/templates/skills_spellbook/might.png new file mode 100644 index 0000000..b46caba Binary files /dev/null and b/assets/templates/skills_spellbook/might.png differ diff --git a/assets/templates/skills_spellbook/oak_sage.png b/assets/templates/skills_spellbook/oak_sage.png new file mode 100644 index 0000000..935949b Binary files /dev/null and b/assets/templates/skills_spellbook/oak_sage.png differ diff --git a/assets/templates/skills_spellbook/prayer.png b/assets/templates/skills_spellbook/prayer.png new file mode 100644 index 0000000..b010f83 Binary files /dev/null and b/assets/templates/skills_spellbook/prayer.png differ diff --git a/assets/templates/skills_spellbook/raven.png b/assets/templates/skills_spellbook/raven.png new file mode 100644 index 0000000..3b76420 Binary files /dev/null and b/assets/templates/skills_spellbook/raven.png differ diff --git a/assets/templates/skills_spellbook/redemption.png b/assets/templates/skills_spellbook/redemption.png new file mode 100644 index 0000000..cfa9b1a Binary files /dev/null and b/assets/templates/skills_spellbook/redemption.png differ diff --git a/assets/templates/skills_spellbook/teleport.png b/assets/templates/skills_spellbook/teleport.png new file mode 100644 index 0000000..f350781 Binary files /dev/null and b/assets/templates/skills_spellbook/teleport.png differ diff --git a/assets/templates/skills_spellbook/tome_of_identify.png b/assets/templates/skills_spellbook/tome_of_identify.png new file mode 100644 index 0000000..ee69238 Binary files /dev/null and b/assets/templates/skills_spellbook/tome_of_identify.png differ diff --git a/assets/templates/skills_spellbook/tome_of_town_portal.png b/assets/templates/skills_spellbook/tome_of_town_portal.png new file mode 100644 index 0000000..819e56b Binary files /dev/null and b/assets/templates/skills_spellbook/tome_of_town_portal.png differ diff --git a/assets/templates/skills_spellbook/vengeance.png b/assets/templates/skills_spellbook/vengeance.png new file mode 100644 index 0000000..fea0b09 Binary files /dev/null and b/assets/templates/skills_spellbook/vengeance.png differ diff --git a/assets/templates/skills_spellbook/vigor.png b/assets/templates/skills_spellbook/vigor.png new file mode 100644 index 0000000..9672646 Binary files /dev/null and b/assets/templates/skills_spellbook/vigor.png differ diff --git a/config/game.ini b/config/game.ini index 9c3362c..fe5908a 100644 --- a/config/game.ini +++ b/config/game.ini @@ -130,6 +130,13 @@ corpse=459,195,414,213 chat_icon=7,555,43,41 left_panel_header=0,0,455,56 right_panel_header=830,0,455,56 +; A CENTRED panel (Chronicle, and anything else that opens mid-screen) puts its +; close button outside both header ROIs above — the Chronicle's X sits at +; (952, 56), inside right_panel_header's x-range but exactly at its 56px height +; boundary, so the match centre falls out. Nothing then closes it, and a +; centred panel blanks every later template search: measured as a 66s +; click_red_portal failure on 2026-08-28 (Bug 31's residual). +center_panel_header=400,0,620,92 npc_dialogue=456,0,30,150 bind_skill=516,619,251,29 quest_skill_btn=284,455,710,121 diff --git a/src/health_manager.py b/src/health_manager.py index 2ef51b7..c9dba61 100644 --- a/src/health_manager.py +++ b/src/health_manager.py @@ -37,6 +37,7 @@ class HealthManager: self._last_chicken_screenshot = None self._count_panel_detects = 0 self._count_wp_panel_detects = 0 + self._count_center_panel_detects = 0 def stop_monitor(self): self._do_monitor = False @@ -202,6 +203,27 @@ class HealthManager: self._last_merc_heal = time.time() # Close any open panels that might block detection + # A CENTRED panel (Chronicle) matches neither header ROI, so + # nothing ever closed it — and a centred panel blanks every + # later template search. Measured 2026-08-28: Chronicle open, + # A5_RED_PORTAL unfindable, 66s approach failure. It is not a + # threat, so escape it without counting toward a chicken, + # bounded the same way as the waypoint panel. + if not self.get_panel_check_paused() and is_visible(ScreenObjects.CenterPanel, img): + self._count_center_panel_detects = getattr(self, "_count_center_panel_detects", 0) + 1 + if self._count_center_panel_detects <= self._MAX_WP_PANEL_ESCAPES: + Logger.debug( + f"Centred panel open (blocks all template matching) — closing it " + f"({self._count_center_panel_detects}/{self._MAX_WP_PANEL_ESCAPES})" + ) + from input_layer import keyboard as kb + kb.send("esc") + wait(0.1, 0.2) + fn_end = time.perf_counter() + wait(max(0.01, (1/15 - (fn_end - fn_start)) * random.uniform(0.8, 1.2))) + continue + Logger.warning("Centred panel would not close — treating as a blocking panel") + if not self.get_panel_check_paused() and (is_visible(ScreenObjects.LeftPanel, img) or is_visible(ScreenObjects.RightPanel, img)): # A waypoint panel is self-inflicted: the pather walked the char # over the WP stone. That is a navigation problem, not a threat, @@ -237,6 +259,7 @@ class HealthManager: else: # Screen is clear, so any waypoint panel we escaped did close. self._count_wp_panel_detects = 0 + self._count_center_panel_detects = 0 fn_end = time.perf_counter() # Target ~15 FPS polling with anti-cheat jitter diff --git a/src/ui_manager.py b/src/ui_manager.py index 919109f..74b5fb0 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -225,6 +225,12 @@ class ScreenObjects: threshold=0.8, use_grayscale=True ) + CenterPanel=ScreenObject( + ref=["CLOSE_PANEL_2", "CLOSE_PANEL"], + roi="center_panel_header", + threshold=0.8, + use_grayscale=True + ) NPCDialogue=ScreenObject( ref="NPC_DIALOGUE", roi="npc_dialogue", diff --git a/test/test_center_panel.py b/test/test_center_panel.py new file mode 100644 index 0000000..5155e50 --- /dev/null +++ b/test/test_center_panel.py @@ -0,0 +1,46 @@ +"""A centred panel must be detected and closed. + +Bug 31 fixed the merc-panel keypress that opened CHRONICLE, but noted the real +gap was never closed: the health manager's guard matches only LeftPanel / +RightPanel, and a centred panel matches neither. Nothing closes it, and a +centred panel blanks EVERY later template search. + +Measured 2026-08-28: Chronicle open, A5_RED_PORTAL unfindable, 66s +click_red_portal failure. The Chronicle's close button scores 1.000 against +CLOSE_PANEL_2 at (952, 56) — inside right_panel_header's x-range but exactly at +its 56px height boundary, so the match centre falls outside the ROI. +""" +import inspect + + +def test_center_panel_screenobject_exists(): + from ui_manager import ScreenObjects + + assert hasattr(ScreenObjects, "CenterPanel"), "no detection for a centred panel" + + +def test_center_panel_roi_covers_the_chronicle_close_button(): + from config import Config + + x, y, w, h = Config().ui_roi["center_panel_header"] + cx, cy = 952, 56 # measured Chronicle X position + assert x <= cx <= x + w, "ROI does not span the Chronicle close button in x" + assert y <= cy <= y + h, ( + "ROI does not span it in y — this is exactly how right_panel_header " + "missed it, its 56px height ending at the button's centre" + ) + + +def test_guard_checks_center_panel_before_chickening(): + """It must be escaped, not counted as a threat. + + A centred panel is self-inflicted UI state, like the waypoint panel. + Chickening on it throws away a healthy game. + """ + from health_manager import HealthManager + + src = inspect.getsource(HealthManager) + assert "ScreenObjects.CenterPanel" in src, "guard never checks for a centred panel" + center_at = src.index("ScreenObjects.CenterPanel") + chicken_at = src.index("Chickening to be safe") + assert center_at < chicken_at, "centred-panel escape must come before the chicken path"