diff --git a/src/npc_manager.py b/src/npc_manager.py index 2a1c41b..7925613 100644 --- a/src/npc_manager.py +++ b/src/npc_manager.py @@ -318,6 +318,9 @@ def open_npc_menu( # Search for npc name tags by hovering to all template locations that are found start = time.time() attempts = 0 + # Clicks taken on a body+pose guess rather than a confirmed name tag. + weak_attempts = 0 + MAX_WEAK_ATTEMPTS = 2 while (time.time() - start) < NPC_SEARCH_TIMEOUT: img = grab() # Fast path: if the target NPC's dialogue is already open, return immediately. @@ -405,7 +408,17 @@ def open_npc_menu( if name_tag_confirmed: Logger.info(f"Clicking on {npc_key} at {mouse.get_position()} (name tag confirmed)") elif body_confident and pose_confirmed: - Logger.info(f"Clicking on {npc_key} at {mouse.get_position()} (body+pose confirmed, name tag score {res_w.score:.3f})") + # Body+pose is a guess, not a confirmation: over the 2026-09-04 session it + # opened the dialogue 5/73 times for Ormus (7%) while the name-tag path hit + # 37/47 (79%). Body score does not separate the two (failures averaged 0.46, + # successes 0.40-0.46, one false positive scored 0.79), so there is nothing + # to threshold on — the only useful lever is to stop spending the search + # window on guesses and reach the grid sweep, which does find the NPC. + if weak_attempts >= MAX_WEAK_ATTEMPTS: + Logger.debug(f"NPC {npc_key} - body+pose guess budget spent, leaving the rest to the grid sweep") + break + weak_attempts += 1 + Logger.info(f"Clicking on {npc_key} at {mouse.get_position()} (body+pose guess {weak_attempts}/{MAX_WEAK_ATTEMPTS}, name tag score {res_w.score:.3f})") else: Logger.debug(f"NPC {npc_key} - skipping pos {result['pos']}: name_tag={name_tag_confirmed} in_roi={in_npc_roi} body={body_confident} pose={pose_confirmed}") continue @@ -421,8 +434,10 @@ def open_npc_menu( if _wait_action_btns(npc_key, timeout=2.5): Logger.debug(f"NPC {npc_key} - dialogue open (action buttons visible)") return True - # Retry once: first click sometimes just turns the NPC toward you. - if attempts <= 2: + # Retry once: first click sometimes just turns the NPC toward you. Only worth + # doing when the name tag confirmed we are actually on the NPC — retrying a + # body+pose guess just pays the click and the 2.5s wait twice for nothing. + if attempts <= 2 and name_tag_confirmed: Logger.debug(f"NPC {npc_key} - action buttons not found, retrying click on body") mouse.move(*result["pos"], randomize=3, delay_factor=[0.2, 0.3]) wait(0.2, 0.3) diff --git a/src/town/town_manager.py b/src/town/town_manager.py index 23e0451..777bb8b 100644 --- a/src/town/town_manager.py +++ b/src/town/town_manager.py @@ -14,7 +14,11 @@ from inventory import personal, vendor, common TOWN_MARKERS = [ "A5_TOWN_0", "A5_TOWN_1", "A5_TOWN_5", "A5_TOWN_6", "A4_TOWN_4", "A4_TOWN_5", - "A3_TOWN_0", "A3_TOWN_1", + # A3_TOWN_14 / _20 carry Act 3: measured against 8 Kurast Docks screenshots + # they hit 0.95 / 0.74, while A3_TOWN_0 and _1 managed 0.21 and 0.29 — the two + # worst of all 20 a3_town templates, i.e. act detection in A3 could not succeed. + # Both are act-unique (<=0.35 against A4 and A5 frames), so they cannot misfire. + "A3_TOWN_14", "A3_TOWN_20", "A3_TOWN_0", "A3_TOWN_1", "A2_TOWN_0", "A2_TOWN_1", "A2_TOWN_10", "A1_TOWN_1", "A1_TOWN_3" ]