fix(health): detect and close CENTRED panels — Chronicle blocked every template match
Bug 31 fixed the keypress that opened CHRONICLE but explicitly left the real
gap open: the panel guard matches only LeftPanel / RightPanel, and a centred
panel matches neither. Nothing closed it, and a centred panel blanks EVERY
later template search.
Recurred 2026-08-28 with the merc key never pressed at all
(resurrect_merc | skip | merc alive), so something else opened it and it simply
stayed. The error screenshot shows Chronicle filling the screen while the bot
hunted A5_RED_PORTAL for 66s:
Traverse from a5_larzuk to a5_nihlathak_portal
Wanted to select A5_RED_PORTAL, but could not find it
random guess towards (96, -115)
Wanted to select A5_RED_PORTAL, but could not find it
Why the existing guard missed it, measured on that frame: the Chronicle's close
button scores 1.000 against CLOSE_PANEL_2 at (952, 56). That x IS inside
right_panel_header (830,0,455,56) — but the ROI is 56px tall and the button's
centre sits exactly at y=56, so the match falls outside. Both header ROIs
scored 0.409 and 0.373.
Adds a center_panel_header ROI (400,0,620,92) and a CenterPanel ScreenObject,
and escapes it WITHOUT counting toward a chicken — a centred panel is
self-inflicted UI state like the waypoint panel, and chickening on it throws
away a healthy game. Bounded by the same _MAX_WP_PANEL_ESCAPES so one that
genuinely will not close still falls through.
Verified on the failure frame: CenterPanel True, LeftPanel False, RightPanel
False.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BIN
assets/templates/skills_spellbook/blessed_hammer.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/templates/skills_spellbook/cleansing.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/templates/skills_spellbook/concentration.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/templates/skills_spellbook/conviction.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/templates/skills_spellbook/defiance.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/templates/skills_spellbook/fist_of_the_heavens.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/templates/skills_spellbook/holy_shield.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/templates/skills_spellbook/might.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/templates/skills_spellbook/oak_sage.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/templates/skills_spellbook/prayer.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/templates/skills_spellbook/raven.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/templates/skills_spellbook/redemption.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/templates/skills_spellbook/teleport.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/templates/skills_spellbook/tome_of_identify.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/templates/skills_spellbook/tome_of_town_portal.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/templates/skills_spellbook/vengeance.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/templates/skills_spellbook/vigor.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
@@ -130,6 +130,13 @@ corpse=459,195,414,213
|
|||||||
chat_icon=7,555,43,41
|
chat_icon=7,555,43,41
|
||||||
left_panel_header=0,0,455,56
|
left_panel_header=0,0,455,56
|
||||||
right_panel_header=830,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
|
npc_dialogue=456,0,30,150
|
||||||
bind_skill=516,619,251,29
|
bind_skill=516,619,251,29
|
||||||
quest_skill_btn=284,455,710,121
|
quest_skill_btn=284,455,710,121
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class HealthManager:
|
|||||||
self._last_chicken_screenshot = None
|
self._last_chicken_screenshot = None
|
||||||
self._count_panel_detects = 0
|
self._count_panel_detects = 0
|
||||||
self._count_wp_panel_detects = 0
|
self._count_wp_panel_detects = 0
|
||||||
|
self._count_center_panel_detects = 0
|
||||||
|
|
||||||
def stop_monitor(self):
|
def stop_monitor(self):
|
||||||
self._do_monitor = False
|
self._do_monitor = False
|
||||||
@@ -202,6 +203,27 @@ class HealthManager:
|
|||||||
self._last_merc_heal = time.time()
|
self._last_merc_heal = time.time()
|
||||||
|
|
||||||
# Close any open panels that might block detection
|
# 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)):
|
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
|
# A waypoint panel is self-inflicted: the pather walked the char
|
||||||
# over the WP stone. That is a navigation problem, not a threat,
|
# over the WP stone. That is a navigation problem, not a threat,
|
||||||
@@ -237,6 +259,7 @@ class HealthManager:
|
|||||||
else:
|
else:
|
||||||
# Screen is clear, so any waypoint panel we escaped did close.
|
# Screen is clear, so any waypoint panel we escaped did close.
|
||||||
self._count_wp_panel_detects = 0
|
self._count_wp_panel_detects = 0
|
||||||
|
self._count_center_panel_detects = 0
|
||||||
|
|
||||||
fn_end = time.perf_counter()
|
fn_end = time.perf_counter()
|
||||||
# Target ~15 FPS polling with anti-cheat jitter
|
# Target ~15 FPS polling with anti-cheat jitter
|
||||||
|
|||||||
@@ -225,6 +225,12 @@ class ScreenObjects:
|
|||||||
threshold=0.8,
|
threshold=0.8,
|
||||||
use_grayscale=True
|
use_grayscale=True
|
||||||
)
|
)
|
||||||
|
CenterPanel=ScreenObject(
|
||||||
|
ref=["CLOSE_PANEL_2", "CLOSE_PANEL"],
|
||||||
|
roi="center_panel_header",
|
||||||
|
threshold=0.8,
|
||||||
|
use_grayscale=True
|
||||||
|
)
|
||||||
NPCDialogue=ScreenObject(
|
NPCDialogue=ScreenObject(
|
||||||
ref="NPC_DIALOGUE",
|
ref="NPC_DIALOGUE",
|
||||||
roi="npc_dialogue",
|
roi="npc_dialogue",
|
||||||
|
|||||||
46
test/test_center_panel.py
Normal file
@@ -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"
|
||||||