fix(event_sweep): enhance error handling with retries for stage-row detection and sweep start click
This commit is contained in:
parent
63ee47d6e7
commit
33dd375f36
@ -8,7 +8,7 @@ Maps each local feature to the corresponding `~/repo/baas-reference/module/...`
|
|||||||
| Cafe | `module/cafe_reward.py` | `to_cafe` (its `relationship_rank_up` popup-handling now also ported, see below), `interaction_for_cafe_solve_method3`, `collect` | `ba_auto/tasks/cafe.py` | `picture.co_detect`/`color.rgb_in_range` → `driver.color_at` pixel-probe checks; sparkle template match ported in-process into `ba_auto/detector.py` (`find_cafe_sparkle`, now multi-scale) | Migrated: real Python, state-verified via color probes (no legacy bridge). Pat loop now polls for the full attempt budget instead of stopping on the first miss (see `plan.md` Phase 6 follow-up) — not yet confirmed against a live sparkle since none was available during testing. `_dismiss_rank_up_if_shown` reuses `navigation.is_on_subscreen` to detect and clear the full-screen bond-rank-up cutscene after a pat (see `plan.md` Phase 6 follow-up: rank-up popups) — not yet live-confirmed against a real trigger |
|
| Cafe | `module/cafe_reward.py` | `to_cafe` (its `relationship_rank_up` popup-handling now also ported, see below), `interaction_for_cafe_solve_method3`, `collect` | `ba_auto/tasks/cafe.py` | `picture.co_detect`/`color.rgb_in_range` → `driver.color_at` pixel-probe checks; sparkle template match ported in-process into `ba_auto/detector.py` (`find_cafe_sparkle`, now multi-scale) | Migrated: real Python, state-verified via color probes (no legacy bridge). Pat loop now polls for the full attempt budget instead of stopping on the first miss (see `plan.md` Phase 6 follow-up) — not yet confirmed against a live sparkle since none was available during testing. `_dismiss_rank_up_if_shown` reuses `navigation.is_on_subscreen` to detect and clear the full-screen bond-rank-up cutscene after a pat (see `plan.md` Phase 6 follow-up: rank-up popups) — not yet live-confirmed against a real trigger |
|
||||||
| Stamina/AP | `module/collect_daily_free_power.py`, `module/collect_daily_task_power.py` | `to_tasks`/`implement` (task-power, ported); `to_purchase_pyroxenes_menu` (free-power, not ported) | `ba_auto/tasks/stamina.py` | `color.rgb_in_range` → `driver.color_at`; reference's per-tab claim loop → live UI's single "一括受取" bulk-claim button + Enter | Partially migrated: Mission-panel claim done (see `plan.md` Phase 8). Daily Free Power (real-money purchase menu) deliberately not automated |
|
| Stamina/AP | `module/collect_daily_free_power.py`, `module/collect_daily_task_power.py` | `to_tasks`/`implement` (task-power, ported); `to_purchase_pyroxenes_menu` (free-power, not ported) | `ba_auto/tasks/stamina.py` | `color.rgb_in_range` → `driver.color_at`; reference's per-tab claim loop → live UI's single "一括受取" bulk-claim button + Enter | Partially migrated: Mission-panel claim done (see `plan.md` Phase 8). Daily Free Power (real-money purchase menu) deliberately not automated |
|
||||||
| Normal/Hard story AP sweep | `module/explore_tasks/sweep_task.py`, `module/explore_tasks/task_utils.py` | `to_region` (ported: OCR region-number readout + delta-click), a scoped-down `swipe_search_target_str` (ported: OCR stage-row label matching), `start_sweep`'s named-outcome contract (ported via `navigation.wait_for_state`, this project's scoped `co_detect` port) | `ba_auto/tasks/story_sweep.py` | OCR region/stage-name matching, ported for real (Phase 10) — replaces Phase 9's "next-region arrow stops advancing, then random stage" heuristic; MAX click verified via `SWEEP_MINUS_BUTTON_PROBE` color check (reused, still correct), modal closed via its own X button (Escape doesn't close it; X-button position re-calibrated per stage-layout variant, see Phase 10) | Done (see `plan.md` Phase 10, supersedes Phase 9). Config-driven exact `(region, stage, count)` targets (`config.STORY_SWEEP_TARGETS`), not latest-region/random-stage. Opt-in only, not in default flow |
|
| Normal/Hard story AP sweep | `module/explore_tasks/sweep_task.py`, `module/explore_tasks/task_utils.py` | `to_region` (ported: OCR region-number readout + delta-click), a scoped-down `swipe_search_target_str` (ported: OCR stage-row label matching), `start_sweep`'s named-outcome contract (ported via `navigation.wait_for_state`, this project's scoped `co_detect` port) | `ba_auto/tasks/story_sweep.py` | OCR region/stage-name matching, ported for real (Phase 10) — replaces Phase 9's "next-region arrow stops advancing, then random stage" heuristic; MAX click verified via `SWEEP_MINUS_BUTTON_PROBE` color check (reused, still correct), modal closed via its own X button (Escape doesn't close it; X-button position re-calibrated per stage-layout variant, see Phase 10) | Done (see `plan.md` Phase 10, supersedes Phase 9). Config-driven exact `(region, stage, count)` targets (`config.STORY_SWEEP_TARGETS`), not latest-region/random-stage. Opt-in only, not in default flow |
|
||||||
| Event sweep | `module/sweep_activity.py`, `module/activities/activity_utils.py` | `activity_sweep` (main flow), `to_activity` (nav to the event's Story/Mission/Challenge tabs), `check_sweep_availability`/`color.check_sweep_availability` (SSS gate), `start_sweep`'s named-outcome contract (shared with story_sweep's, ported the same way via `navigation.wait_for_state`) | `ba_auto/tasks/event_sweep.py`, `ba_auto/navigation.py` (`return_to_home`) | `to_activity`'s bottom-nav-icon entry -> this client's home-screen event badge (`config.EVENT_BADGE_ICON`, confirmed live to be a rotating carousel -- see Status); the reference's config-string sweep-list parsing (arbitrary stage lists, per-stage float/fraction counts via `preprocess_activity_region`/`preprocess_activity_sweep_times`) -> a single date-ordinal-modulo rotation target over a fixed 9-12 sub-range, mirroring `story_sweep.py`'s own rotation, per explicit user direction; stage-number OCR (`detector.read_int`) replaces the reference's `swipe_search_target_str` template-button search, since this client's stage list only ever needs its bottom scroll extreme for the 9-12 target range | Done, with a live-reported bug fixed (see `plan.md`'s Phase 14 follow-up). Live-calibrated against nik-gpu 2026-07-10 against the currently-running "鉄道爆走事件" event (12 stages) -- zero real AP spent during calibration (every confirm dialog reached was cancelled via Escape, verified by the AP counter). The stage-info modal is structurally identical to story_sweep's (MIN/-/+/MAX stepper, same AP-confirm dialog reusing `SWEEP_CONFIRM_*`/`SWEEP_RESULT_BUTTON_REGION`), but simpler: no region navigation, one fixed modal layout confirmed across two different stages (09 and 12), and the modal closes on Escape (story_sweep's doesn't). The reference's SSS-availability gate for a never-cleared stage was never exercised (every stage 9-12 on this account was already 3-starred) -- `event_sweep.py` handles that gate the same defensive way story_sweep handles an unavailable target: if the MAX-button count-raise can't be verified, it aborts without spending AP rather than guessing. **Bug found on a real run**: the home-screen event badge turned out to be a rotating carousel (cycles between the current event's countdown and other notices, e.g. a finished event's leftover reward-claim reminder), so a click could land on a stale event's page instead. Fixed via a new shared `navigation.return_to_home` primitive (bounded press-back-until-home loop, built generically so other tasks can reuse it, per explicit user request) plus wrong-page detection in `_find_stage_row` (zero stage-row numbers OCR'd at all -> retry via `return_to_home`, up to 3 attempts). **Second bug found on the next real run** (after the above fix correctly recovered and correctly OCR'd the target row): the row's own 入場 (enter) button click had no retry, unlike every other click-then-confirm step in this module -- missed once, aborted the whole run. Fixed via `_open_stage_modal`, the same click-then-verify-then-retry pattern already used everywhere else in the file. Neither fix has been live-tested against a real recurrence yet. |
|
| Event sweep | `module/sweep_activity.py`, `module/activities/activity_utils.py` | `activity_sweep` (main flow), `to_activity` (nav to the event's Story/Mission/Challenge tabs), `check_sweep_availability`/`color.check_sweep_availability` (SSS gate), `start_sweep`'s named-outcome contract (shared with story_sweep's, ported the same way via `navigation.wait_for_state`) | `ba_auto/tasks/event_sweep.py`, `ba_auto/navigation.py` (`return_to_home`) | `to_activity`'s bottom-nav-icon entry -> this client's home-screen event badge (`config.EVENT_BADGE_ICON`, confirmed live to be a rotating carousel -- see Status); the reference's config-string sweep-list parsing (arbitrary stage lists, per-stage float/fraction counts via `preprocess_activity_region`/`preprocess_activity_sweep_times`) -> a single date-ordinal-modulo rotation target over a fixed 9-12 sub-range, mirroring `story_sweep.py`'s own rotation, per explicit user direction; stage-number OCR (`detector.read_int`) replaces the reference's `swipe_search_target_str` template-button search, since this client's stage list only ever needs its bottom scroll extreme for the 9-12 target range | Done, with a live-reported bug fixed (see `plan.md`'s Phase 14 follow-up). Live-calibrated against nik-gpu 2026-07-10 against the currently-running "鉄道爆走事件" event (12 stages) -- zero real AP spent during calibration (every confirm dialog reached was cancelled via Escape, verified by the AP counter). The stage-info modal is structurally identical to story_sweep's (MIN/-/+/MAX stepper, same AP-confirm dialog reusing `SWEEP_CONFIRM_*`/`SWEEP_RESULT_BUTTON_REGION`), but simpler: no region navigation, one fixed modal layout confirmed across two different stages (09 and 12), and the modal closes on Escape (story_sweep's doesn't). The reference's SSS-availability gate for a never-cleared stage was never exercised (every stage 9-12 on this account was already 3-starred) -- `event_sweep.py` handles that gate the same defensive way story_sweep handles an unavailable target: if the MAX-button count-raise can't be verified, it aborts without spending AP rather than guessing. **Bug found on a real run**: the home-screen event badge turned out to be a rotating carousel (cycles between the current event's countdown and other notices, e.g. a finished event's leftover reward-claim reminder), so a click could land on a stale event's page instead. Fixed via a new shared `navigation.return_to_home` primitive (bounded press-back-until-home loop, built generically so other tasks can reuse it, per explicit user request) plus wrong-page detection in `_find_stage_row` (zero stage-row numbers OCR'd at all -> retry via `return_to_home`, up to 3 attempts). **Second bug found on the next real run** (after the above fix correctly recovered and correctly OCR'd the target row): the row's own 入場 (enter) button click had no retry, unlike every other click-then-confirm step in this module -- missed once, aborted the whole run. Fixed via `_open_stage_modal`, the same click-then-verify-then-retry pattern already used everywhere else in the file. **Third bug found on a third real run**: `_find_stage_row`'s wrong-page detection false-positived twice (a fresh navigation's list hadn't finished rendering on the first OCR pass) before self-correcting on the 3rd attempt, wasting expensive return-home retries on what was really just a timing race -- fixed with a cheap in-place rescan before concluding "wrong page." Also, once past that, the 掃討開始 (start sweep) click turned out to be the last bare, unretried click in the file -- fixed via `_click_sweep_start_and_verify`. None of these three fixes has been live-tested against a real recurrence yet. |
|
||||||
| Group/Club AP | `module/group.py` | Need to inspect | `ba_auto/tasks/group.py` | fixed click + state check via local driver | Not started |
|
| Group/Club AP | `module/group.py` | Need to inspect | `ba_auto/tasks/group.py` | fixed click + state check via local driver | Not started |
|
||||||
| Bounty | `module/rewarded_task.py` | Need to inspect | `ba_auto/tasks/bounty.py` | sweep/color/OCR adaptation | Not started |
|
| Bounty | `module/rewarded_task.py` | Need to inspect | `ba_auto/tasks/bounty.py` | sweep/color/OCR adaptation | Not started |
|
||||||
| Commissions | `module/clear_special_task_power.py` | Need to inspect | `ba_auto/tasks/commission.py` | sweep/color adaptation | Not started |
|
| Commissions | `module/clear_special_task_power.py` | Need to inspect | `ba_auto/tasks/commission.py` | sweep/color adaptation | Not started |
|
||||||
|
|||||||
@ -47,6 +47,26 @@ click-then-check with no retry, unlike every other click-then-confirm step
|
|||||||
in this module. _open_stage_modal now retries it the same way, matching
|
in this module. _open_stage_modal now retries it the same way, matching
|
||||||
CLAUDE.md's own documented history of the mailbox/cafe icons missing their
|
CLAUDE.md's own documented history of the mailbox/cafe icons missing their
|
||||||
first click and working on retry.
|
first click and working on retry.
|
||||||
|
|
||||||
|
A third live run surfaced two more instances of the same underlying
|
||||||
|
problem class:
|
||||||
|
|
||||||
|
1. _find_stage_row's "wrong page" detection (all 5 rows OCR empty) fired
|
||||||
|
as a FALSE POSITIVE twice in a row on a run that was actually on the
|
||||||
|
right page the whole time -- the Quest tab's stage list evidently
|
||||||
|
hadn't finished rendering yet on the first OCR pass right after
|
||||||
|
navigating there fresh, and 1 second wasn't always enough settle time.
|
||||||
|
Burning a full return-home-and-re-navigate cycle (the "wrong page"
|
||||||
|
recovery path) on a pure rendering race wastes retry budget that should
|
||||||
|
be reserved for an actually-wrong page. _find_stage_row now does a
|
||||||
|
cheap in-place rescan (re-read the same rows, no re-scroll/re-navigate)
|
||||||
|
a couple of times before concluding "no valid rows at all."
|
||||||
|
2. Once past that, the MAX-count-raise succeeded (on its own internal
|
||||||
|
retry) but the following 掃討開始 (start sweep) click was, again, a bare
|
||||||
|
single click with no retry -- the click missed, no confirm dialog ever
|
||||||
|
appeared, and the whole run aborted right at the last step before
|
||||||
|
actually spending AP. _click_sweep_start_and_verify now wraps it the
|
||||||
|
same way as every other click in this file.
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@ -60,6 +80,9 @@ MODAL_CLOSE_RETRIES = 3
|
|||||||
WRONG_PAGE_RETRIES = 3
|
WRONG_PAGE_RETRIES = 3
|
||||||
WRONG_PAGE_RETRY_WAIT = 3
|
WRONG_PAGE_RETRY_WAIT = 3
|
||||||
STAGE_ENTER_RETRIES = 3
|
STAGE_ENTER_RETRIES = 3
|
||||||
|
STAGE_ROW_SCAN_ATTEMPTS = 3
|
||||||
|
STAGE_ROW_SCAN_RETRY_WAIT = 1.5
|
||||||
|
SWEEP_START_RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
def _is_stage_modal_open(driver, config):
|
def _is_stage_modal_open(driver, config):
|
||||||
@ -116,6 +139,18 @@ def _row_number_rect(config, row_y):
|
|||||||
return (x1, row_y - top_pad, x2, row_y + bottom_pad)
|
return (x1, row_y - top_pad, x2, row_y + bottom_pad)
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_stage_rows_once(driver, config, stage):
|
||||||
|
saw_any_valid_row = False
|
||||||
|
for row_y in config.EVENT_STAGE_ROW_Y:
|
||||||
|
label = detector.read_int(_row_number_rect(config, row_y))
|
||||||
|
print(f"[event_sweep] row @ {row_y}: read '{label}'")
|
||||||
|
if label is not None:
|
||||||
|
saw_any_valid_row = True
|
||||||
|
if label == stage:
|
||||||
|
return row_y, saw_any_valid_row
|
||||||
|
return None, saw_any_valid_row
|
||||||
|
|
||||||
|
|
||||||
def _find_stage_row(driver, config, stage):
|
def _find_stage_row(driver, config, stage):
|
||||||
# This event's target range (9-12) always sits within the last 5 rows,
|
# This event's target range (9-12) always sits within the last 5 rows,
|
||||||
# confirmed live regardless of starting scroll position -- so unlike
|
# confirmed live regardless of starting scroll position -- so unlike
|
||||||
@ -126,19 +161,26 @@ def _find_stage_row(driver, config, stage):
|
|||||||
# stage-row cards, so all 5 reads coming back empty is a strong signal
|
# stage-row cards, so all 5 reads coming back empty is a strong signal
|
||||||
# we're on the wrong page entirely (see module docstring), distinct from
|
# we're on the wrong page entirely (see module docstring), distinct from
|
||||||
# "right page, this stage just isn't among the visible rows."
|
# "right page, this stage just isn't among the visible rows."
|
||||||
|
#
|
||||||
|
# A fresh navigation's list can still be settling/rendering when the
|
||||||
|
# first OCR pass runs -- confirmed live, this produced two consecutive
|
||||||
|
# false-positive "wrong page" reads on a run that was actually on the
|
||||||
|
# right page the whole time. Rescan in place (no re-scroll/re-navigate)
|
||||||
|
# a couple of times before concluding "no valid rows at all" -- cheap
|
||||||
|
# compared to the caller's much more expensive return-home-and-retry
|
||||||
|
# recovery, which should be reserved for an actually-wrong page.
|
||||||
x, y = config.EVENT_STAGE_LIST_SCROLL_POINT
|
x, y = config.EVENT_STAGE_LIST_SCROLL_POINT
|
||||||
driver.scroll(x, y, "down", config.EVENT_STAGE_LIST_SCROLL_CLICKS)
|
driver.scroll(x, y, "down", config.EVENT_STAGE_LIST_SCROLL_CLICKS)
|
||||||
driver.wait(0.5)
|
driver.wait(0.5)
|
||||||
|
|
||||||
saw_any_valid_row = False
|
for attempt in range(1, STAGE_ROW_SCAN_ATTEMPTS + 1):
|
||||||
for row_y in config.EVENT_STAGE_ROW_Y:
|
row_y, saw_any_valid_row = _scan_stage_rows_once(driver, config, stage)
|
||||||
label = detector.read_int(_row_number_rect(config, row_y))
|
if row_y is not None or saw_any_valid_row:
|
||||||
print(f"[event_sweep] row @ {row_y}: read '{label}'")
|
|
||||||
if label is not None:
|
|
||||||
saw_any_valid_row = True
|
|
||||||
if label == stage:
|
|
||||||
return row_y, saw_any_valid_row
|
return row_y, saw_any_valid_row
|
||||||
return None, saw_any_valid_row
|
print(f"[event_sweep] no stage-row numbers recognized on scan {attempt}/{STAGE_ROW_SCAN_ATTEMPTS} -- screen may still be settling")
|
||||||
|
if attempt < STAGE_ROW_SCAN_ATTEMPTS:
|
||||||
|
driver.wait(STAGE_ROW_SCAN_RETRY_WAIT)
|
||||||
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def _click_max_and_verify(driver, config):
|
def _click_max_and_verify(driver, config):
|
||||||
@ -219,6 +261,21 @@ def _open_stage_modal(driver, config, row_y):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _click_sweep_start_and_verify(driver, config):
|
||||||
|
# Same missed-click failure class as _open_stage_modal -- a bare single
|
||||||
|
# click here was found live to leave neither the AP-usage-confirm nor
|
||||||
|
# the AP-purchase dialog detected, aborting the run at the very last
|
||||||
|
# step before it would have actually spent AP. Retry-with-verify like
|
||||||
|
# every other click in this module.
|
||||||
|
for attempt in range(1, SWEEP_START_RETRIES + 1):
|
||||||
|
driver.click(*config.EVENT_SWEEP_START_BUTTON)
|
||||||
|
driver.wait(1.5)
|
||||||
|
if _is_sweep_usage_confirm(driver, config) or _is_ap_purchase_prompt(driver, config):
|
||||||
|
return True
|
||||||
|
print(f"[event_sweep] sweep confirm/AP-purchase dialog not detected after 掃討開始 click (attempt {attempt}/{SWEEP_START_RETRIES})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _sweep_target(driver, config, stage, count):
|
def _sweep_target(driver, config, stage, count):
|
||||||
print(f"[event_sweep] --- target stage {stage} x {count} ---")
|
print(f"[event_sweep] --- target stage {stage} x {count} ---")
|
||||||
|
|
||||||
@ -239,8 +296,10 @@ def _sweep_target(driver, config, stage, count):
|
|||||||
_close_stage_modal(driver, config)
|
_close_stage_modal(driver, config)
|
||||||
return "not_sweepable"
|
return "not_sweepable"
|
||||||
|
|
||||||
driver.click(*config.EVENT_SWEEP_START_BUTTON)
|
if not _click_sweep_start_and_verify(driver, config):
|
||||||
driver.wait(1.5)
|
print("[event_sweep] sweep-usage confirmation not detected, aborting without further input")
|
||||||
|
_close_stage_modal(driver, config)
|
||||||
|
return "unrecognized_state"
|
||||||
|
|
||||||
if _is_ap_purchase_prompt(driver, config):
|
if _is_ap_purchase_prompt(driver, config):
|
||||||
print("[event_sweep] insufficient AP for this sweep -- cancelling without purchasing")
|
print("[event_sweep] insufficient AP for this sweep -- cancelling without purchasing")
|
||||||
@ -249,11 +308,6 @@ def _sweep_target(driver, config, stage, count):
|
|||||||
_close_stage_modal(driver, config)
|
_close_stage_modal(driver, config)
|
||||||
return "inadequate_ap"
|
return "inadequate_ap"
|
||||||
|
|
||||||
if not _is_sweep_usage_confirm(driver, config):
|
|
||||||
print("[event_sweep] sweep-usage confirmation not detected, aborting without further input")
|
|
||||||
_close_stage_modal(driver, config)
|
|
||||||
return "unrecognized_state"
|
|
||||||
|
|
||||||
driver.click(*config.SWEEP_CONFIRM_BUTTON)
|
driver.click(*config.SWEEP_CONFIRM_BUTTON)
|
||||||
driver.wait(1.5)
|
driver.wait(1.5)
|
||||||
print("[event_sweep] sweep confirmed, waiting for results")
|
print("[event_sweep] sweep confirmed, waiting for results")
|
||||||
|
|||||||
9
plan.md
9
plan.md
@ -503,6 +503,15 @@ This is the same failure class `CLAUDE.md` already documents for the mailbox/caf
|
|||||||
|
|
||||||
**Not yet live-tested**: syntax/compile-checked and deployed only.
|
**Not yet live-tested**: syntax/compile-checked and deployed only.
|
||||||
|
|
||||||
|
#### Phase 14 follow-up #3: false-positive wrong-page detection + one more missing retry
|
||||||
|
|
||||||
|
**Reported by the user after a third real run.** Two more instances of the exact same problem class surfaced in one log:
|
||||||
|
|
||||||
|
1. **`_find_stage_row`'s wrong-page detection false-positived twice in a row** on a run that turned out to be on the right page the whole time. The Quest tab's stage list evidently hadn't finished rendering on the first OCR pass right after a fresh navigation (all 5 rows OCR'd empty), and the existing 1-second settle wait wasn't always enough. Since "wrong page" triggers the *expensive* recovery path (`navigation.return_to_home` + a full re-navigate, ~3+ seconds each), this wasted 2 of the outer loop's 3 attempts on what was actually just a rendering race, before the 3rd attempt (now naturally a bit later, giving the UI more time to settle) read all 5 rows correctly. Fixed by adding a *cheap* in-place rescan inside `_find_stage_row` itself (`STAGE_ROW_SCAN_ATTEMPTS = 3`, re-reading the same rows with a short wait between, no re-scroll/re-navigate) before concluding "no valid rows at all" — reserving the expensive return-home recovery for a genuinely wrong page, not a timing race.
|
||||||
|
2. **Once past that, `_click_max_and_verify` correctly raised the sweep count to MAX (on its own internal retry) but the very next click — 掃討開始 (start sweep) — was still a bare single click with no retry**, the last unguarded click in the whole file. It missed once, no confirm dialog (neither the AP-usage-confirm nor the AP-purchase variant) was ever detected, and the run aborted right before it would have actually spent AP. Fixed via new `_click_sweep_start_and_verify`, the same click-then-verify-then-retry pattern (`SWEEP_START_RETRIES = 3`) as every other click in this file — checking for *either* dialog variant, since which one appears depends on current AP.
|
||||||
|
|
||||||
|
**Not yet live-tested**: syntax/compile-checked and deployed only. Every click in `_sweep_target`'s critical path now follows the same retry pattern; if a future run still fails, the next place to look is `SWEEP_CONFIRM_BUTTON`'s own click (the actual AP-spend-committing click) or `_watch_sweep_result`'s polling loop, the two remaining steps that haven't yet been individually implicated by a real failure.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
### OCR
|
### OCR
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user