From a86003cf66ec9bdb4823ef252fe154abecb3ef11 Mon Sep 17 00:00:00 2001 From: Nik Afiq Date: Tue, 14 Jul 2026 20:49:10 +0900 Subject: [PATCH] fix(event_sweep): resolve overlapping button regions to prevent result page closure during sweeps --- ba_auto/config.py | 36 +++++++++++++++++++- ba_auto/tasks/event_sweep.py | 65 +++++++++++++++++++++++++++++++++++- plan.md | 10 ++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/ba_auto/config.py b/ba_auto/config.py index eb0e797..d517d48 100644 --- a/ba_auto/config.py +++ b/ba_auto/config.py @@ -337,7 +337,41 @@ EVENT_STAGE_MODAL_CLOSE_BUTTON = (1704, 271) # x=1150 sits well inside it) while excluding the character-art panel, # confirmed against the same saved false-positive screenshot returning # no match afterward. -EVENT_SWEEP_RESULT_BUTTON_REGION = (1000, 700, 1300, 1050) +# +# A SECOND contamination source found live (2026-07-13, reported by the +# user: "the sweep went well, but I think it overclick and closed the +# result page"): this region's y1=700 overlaps EVENT_SWEEP_START_BUTTON's +# (1400,668) own real cyan-pixel footprint (y 622-715, x 1152-1655 -- +# measured directly during bounty.py's own identical-bug investigation, +# since that modal is confirmed pixel-identical to this one, sharing this +# exact button position). Once the real "掃討完了" result dialog is +# dismissed and the flow lands back on the bare stage-info modal (matching +# this function's own second `ends` condition), 掃討開始 becomes visible +# and cyan again -- _find_result_button re-matched its corner, and +# click_result_button clicked it, re-opening a fresh AP-usage-confirm +# dialog that this loop then had no way to recognize as anything other +# than "still a result button showing," matching the user's own "overclick +# closed the result page" diagnosis exactly. y1 shifted to 730 (15px clear +# of the button's measured 715 bottom edge) to exclude it -- kept wide +# enough (through y2=1050) to still cover both a possible SKIP button and +# the final OK button of a bulk/MAX sweep's reveal sequence (~120px apart +# per story_sweep's own established pattern), since this event's own SKIP +# button was never individually pixel-measured. Not yet re-confirmed live +# with a fresh real sweep -- see EVENT_SWEEP_RESULT_BUTTON_MIN_PIXELS' +# comment for the second half of this fix and its own live-test status. +EVENT_SWEEP_RESULT_BUTTON_REGION = (1000, 730, 1300, 1050) + +# A THIRD contamination source, same class bounty.py's own investigation +# found in its pixel-identical modal: the stage-info modal's own "獲得期待 +# 報酬" reward-icon-preview artwork has a handful of pixels that +# incidentally fall inside SWEEP_CONFIRM_CYAN's broad range even with no +# dialog open. detector.find_color_centroid's min_pixels parameter filters +# this out. Value carried over from BOUNTY_RESULT_BUTTON_MIN_PIXELS by +# analogy (same shared UI component) rather than freshly measured against +# this event's own region -- its search area here is somewhat larger, so +# this may need tuning once a fresh live sweep confirms whether it's +# actually well-calibrated for this specific region. +EVENT_SWEEP_RESULT_BUTTON_MIN_PIXELS = 3000 # Rotation target: which of stages 9-12 to sweep today, per explicit user # request (2026-07-10) -- "the current event has up to 12 stages, randomly diff --git a/ba_auto/tasks/event_sweep.py b/ba_auto/tasks/event_sweep.py index dbb22a7..ec9df1e 100644 --- a/ba_auto/tasks/event_sweep.py +++ b/ba_auto/tasks/event_sweep.py @@ -215,6 +215,41 @@ re-confirmed against a fresh real bulk sweep (that day's AP was reduced to 14/240 by the sweep that surfaced this, too low to force another MAX sweep) -- the fix is validated against the actual recorded false-positive screenshot and the same live code path, just not a full new live sweep. + +The x-narrowing above turned out to not be the whole story either. A real +run (2026-07-13, user report: "the sweep went well, but I think it +overclick and closed the result page") again logged "unrecognized_state" +despite a real successful MAX sweep (rows 08-12 all read correctly, sweep +confirmed). Root cause this time: EVENT_SWEEP_RESULT_BUTTON_REGION's +y1=700 still overlapped EVENT_SWEEP_START_BUTTON's (1400,668) own real +cyan-pixel footprint (y 622-715, x 1152-1655) -- known precisely without +needing a fresh live measurement, since bounty.py's own investigation into +the identical bug class (its stage-info modal is confirmed pixel-identical +to this one, sharing this exact button position) already measured it +directly from a real screenshot. Once the real "掃討完了" result dialog is +dismissed and the flow lands back on the bare stage-info modal (this +function's own second `ends` condition), 掃討開始 becomes visible and +cyan again -- _find_result_button re-matched its corner and clicked it, +re-opening a fresh AP-usage-confirm dialog this loop had no way to +recognize as anything but "still a result button showing," exactly +matching the user's own diagnosis. Fixed two ways, both ported directly +from bounty.py's own resolution of the identical bug: (1) +EVENT_SWEEP_RESULT_BUTTON_REGION's y1 shifted from 700 to 730 (15px clear +of the button's measured 715 bottom edge), kept wide through y2=1050 to +still cover a possible SKIP-then-OK sequence's ~120px vertical spread, +since this event's own SKIP button was never individually pixel-measured; +(2) detector.find_color_centroid's min_pixels parameter (added for +bounty.py's own second contamination source -- sparse stray pixels in the +modal's own reward-icon artwork) applied here too via +EVENT_SWEEP_RESULT_BUTTON_MIN_PIXELS, carried over from +BOUNTY_RESULT_BUTTON_MIN_PIXELS by analogy rather than freshly measured +against this larger region. _watch_sweep_result also gained a third +`ends` condition on _is_ap_purchase_prompt (mirroring bounty.py's +_is_ticket_purchase_prompt fix) as a second, independent safety layer: if +a purchase prompt is ever reached here anyway, it's recognized and +cancelled explicitly by _sweep_target rather than left to the blind color +search. Not yet re-confirmed live -- deployed but no fresh real sweep run +since. """ import datetime import os @@ -254,7 +289,10 @@ def _is_ap_purchase_prompt(driver, config): def _find_result_button(driver, config): - return detector.find_color_centroid(config.EVENT_SWEEP_RESULT_BUTTON_REGION, *config.SWEEP_CONFIRM_CYAN) + return detector.find_color_centroid( + config.EVENT_SWEEP_RESULT_BUTTON_REGION, *config.SWEEP_CONFIRM_CYAN, + min_pixels=config.EVENT_SWEEP_RESULT_BUTTON_MIN_PIXELS, + ) def _count_raised_above_one(driver, config): @@ -432,6 +470,22 @@ def _watch_sweep_result(driver, config): # SKIP/OK sequence has even started) still can't be mistaken for # "swept" -- only "modal gone after we've actually clicked through # something" counts. + # + # A THIRD ends condition, added 2026-07-13 after a real user report + # ("the sweep went well, but I think it overclick and closed the + # result page"): EVENT_SWEEP_RESULT_BUTTON_REGION used to overlap + # EVENT_SWEEP_START_BUTTON's own real footprint (see that config + # constant's comment), so once the real result dialog was dismissed and + # the bare stage-info modal reappeared, _find_result_button could + # re-match 掃討開始 itself and click it -- re-opening a fresh AP-usage- + # confirm dialog this loop had no way to recognize as anything but + # "still a result button." The region is now fixed to exclude that + # button's footprint, but this explicit check (mirroring bounty.py's + # own identical `_is_ticket_purchase_prompt` fix for the same bug + # class) is a second, independent layer: if a purchase/insufficient-AP + # prompt is ever reached here anyway, for any reason, it's recognized + # and handled explicitly by the caller rather than left to a blind + # color search that could otherwise re-click into it. clicked_any = {"value": False} def click_result_button(d): @@ -442,6 +496,7 @@ def _watch_sweep_result(driver, config): d.wait(1.5) ends = { + (lambda d, c: _is_ap_purchase_prompt(d, c)): "prompted_to_purchase", (lambda d, c: _is_stage_modal_open(d, c) and _find_result_button(d, c) is None): "swept", (lambda d, c: clicked_any["value"] and not _is_stage_modal_open(d, c) and _find_result_button(d, c) is None): "swept", } @@ -525,6 +580,14 @@ def _sweep_target(driver, config, stage, count): outcome = _watch_sweep_result(driver, config) print(f"[event_sweep] result: {outcome}") + if outcome == "prompted_to_purchase": + # See _watch_sweep_result's own comment -- cancel this dialog + # explicitly rather than let _close_stage_modal's plain Escape loop + # be the only thing standing between it and a real AP purchase. + print("[event_sweep] AP-purchase prompt detected after the sweep -- cancelling without purchasing") + driver.click(*config.SWEEP_CONFIRM_CANCEL_BUTTON) + driver.wait(1) + if not _close_stage_modal(driver, config): print("[event_sweep] warning: could not confirm stage info modal closed -- leaving it open rather than pressing further keys blindly") return outcome diff --git a/plan.md b/plan.md index 41e8cfa..b7f37e8 100644 --- a/plan.md +++ b/plan.md @@ -584,6 +584,16 @@ This is the same failure class `CLAUDE.md` already documents for the mailbox/caf **Status**: both bugs fixed. The originally reported bug (stage 9 unreadable) is fully confirmed via a real successful live sweep. The `_watch_sweep_result` mislabeling's real root cause is now understood and fixed (previous fix attempt addressed a real but secondary issue); confirmed against the actual live false-positive condition, awaiting one more full live sweep to confirm the outcome log itself reads `"swept"` next time AP allows. +#### Phase 14 follow-up #7: `_find_result_button` re-clicking 掃討開始 itself (2026-07-13) + +**Reported**: a real MAX sweep of stage 12 completed successfully (all 5 stage rows OCR'd correctly, sweep confirmed) but still logged `unrecognized_state`. The user's own diagnosis: "the sweep went well, but I think it overclick and closed the result page." + +Follow-up #6's x-narrowing (1000-1300) turned out to not be the whole fix. Root cause, identified without needing a fresh live sweep: `EVENT_SWEEP_RESULT_BUTTON_REGION`'s y1=700 still overlapped `EVENT_SWEEP_START_BUTTON`'s (1400,668) own real cyan-pixel footprint (y 622-715, x 1152-1655) — this is the **exact same bug class** just fixed in `bounty.py` (see Phase 15's own writeup below), and since bounty's stage-info modal is confirmed pixel-identical to this one (sharing this exact button position), its already-measured footprint could be reused directly rather than re-deriving it. Once the real "掃討完了" result dialog is dismissed and the flow lands back on the bare stage-info modal, 掃討開始 becomes visible and cyan again — `_find_result_button` re-matched its corner and clicked it, re-opening a fresh AP-usage-confirm dialog the loop had no way to recognize as anything but "still a result button," exactly matching the user's own diagnosis. + +**Fixed by porting bounty.py's own resolution directly**: (1) `EVENT_SWEEP_RESULT_BUTTON_REGION`'s y1 shifted 700→730 (15px clear of the button's measured 715 edge), kept wide through y2=1050 to still cover a possible SKIP-then-OK sequence's ~120px spread since this event's own SKIP button was never individually measured; (2) `detector.find_color_centroid`'s `min_pixels` parameter applied here too via a new `EVENT_SWEEP_RESULT_BUTTON_MIN_PIXELS` (3000, carried over from `BOUNTY_RESULT_BUTTON_MIN_PIXELS` by analogy — same shared UI component, though this region is somewhat larger so it may need its own tuning); (3) `_watch_sweep_result` gained a third `ends` condition on `_is_ap_purchase_prompt` (mirroring bounty's `_is_ticket_purchase_prompt` fix) as an independent safety layer. + +**Confirmed live at zero AP cost**: opened the real stage-12 modal (view only, no count raised, no sweep confirmed) and directly compared `_find_result_button` against the OLD region (falsely matched `(1226, 707)`, sitting right on 掃討開始's own corner) vs. the NEW region+min_pixels (correctly returned `None` on the same resting screen). **Not yet re-confirmed with a fresh full real sweep** reaching an actual `"swept"` log — that requires spending real AP again, deferred pending the user's go-ahead. + ### Phase 15: Bounty Reference: `module/rewarded_task.py`. Local: `ba_auto/tasks/bounty.py`.