Refactor gem shop task for UI redesign and improve dialog handling
- Recalibrated gem shop task after client UI update, replacing the old overlay dialog with a full subscreen. - Removed custom dialog open/close checks in favor of shared navigation functions. - Updated method names and logic to reflect new tab structure and improve clarity. - Enhanced error handling for dialog closure and navigation return to home. - Verified live against real account states to ensure functionality and correctness.
This commit is contained in:
parent
bf87a097c3
commit
8d3da6e6e1
@ -1298,95 +1298,82 @@ BOUNTY_SWEEP_CONFIRM_TEXT_RECT = (605, 505, 1320, 615)
|
||||
# state below reduces to a clean, high-contrast flat color rather than a
|
||||
# complex shape needing find_cafe_sparkle-style matching.
|
||||
#
|
||||
# All coordinates/colors below are pixel-scanned from real scrot captures on
|
||||
# nik-gpu at the native 1920x1200 (not estimated from the non-native-resolution
|
||||
# screenshots/gem_shop/*.png reference photos -- confirmed those are NOT 1:1
|
||||
# with real game coordinates, same finding as screenshots/cafe/student/'s own
|
||||
# calibration history). Live-calibrated 2026-07-15 by manually driving the
|
||||
# real dialog end-to-end (see scratchpad/gem_shop_*.png for the captures) --
|
||||
# this walk-through also genuinely claimed the account's real free package
|
||||
# for the day (0 yen, +10 AP / +10,000 credits, confirmed via before/after
|
||||
# counter values), so the two states below are both real, not guessed.
|
||||
# RECALIBRATED 2026-07-29 after a real Blue Archive client UI update changed
|
||||
# this screen's whole layout. Re-verified live on nik-gpu the same day (see
|
||||
# scratchpad/gem_shop_after_click.png / gem_shop_weekly_tab.png /
|
||||
# gem_shop_after_close.png). What changed from the original 2026-07-15
|
||||
# calibration:
|
||||
# - The 期間限定/青輝石/パッケージ 3-top-tab layout is GONE. The dialog is
|
||||
# now a real full subscreen (own header + top-left back arrow at the
|
||||
# same (85, 55) navigation.BACK_BUTTON every other subscreen uses, not
|
||||
# an overlay on top of home), with a left sidebar
|
||||
# (おすすめの商品/限定商品/一般商品/定額商品/青輝石/特別支援) and, for
|
||||
# 一般商品 specifically, a further デイリー/ウィークリー/マンスリー
|
||||
# switch top-right. The daily free package lives under
|
||||
# 一般商品 -> デイリー.
|
||||
# - Because it's now a genuine subscreen, navigation.is_on_subscreen
|
||||
# correctly reads True here (confirmed live: SUBSCREEN_HEADER_PROBE read
|
||||
# (247,250,252) on this screen vs (50,57,137) on true home) and
|
||||
# navigation.return_to_home correctly closes it (confirmed live) -- the
|
||||
# old GEM_SHOP_DIALOG_PROBES workaround below is no longer needed at all
|
||||
# and has been removed; gem_shop.py now shares the same
|
||||
# is_on_subscreen/return_to_home machinery as mailbox/cafe/shop/lesson.
|
||||
# - The status-bar/button coordinates all moved (card is taller, sits
|
||||
# higher). GEM_SHOP_STATUS_CLAIMED_RGB was re-read directly off today's
|
||||
# real "already claimed" state. GEM_SHOP_STATUS_AVAILABLE_RGB could NOT
|
||||
# be re-read off the free card itself (already claimed for the day by
|
||||
# the time of this recalibration) -- instead it was read off three
|
||||
# other real, currently-purchasable cards under the ウィークリー tab
|
||||
# (レポートパッケージ/レポートパッケージ(Lite)/強化珠パッケージ, all
|
||||
# three independently reading the identical (226,236,246)), since Blue
|
||||
# Archive's shop UI reuses one shared "N times purchasable" status-bar
|
||||
# style across every card in this screen, not a per-card color. This is
|
||||
# an inferred-but-live-observed value, not a guess -- treat as
|
||||
# implemented-but-not-yet-confirmed against the daily free card itself
|
||||
# specifically, same disclosed-gap shape the original calibration had
|
||||
# for the "available -> claim" path in general. Re-confirm the exact
|
||||
# first time gem_shop actually claims a package post-redesign.
|
||||
GEM_SHOP_ICON = (204, 345)
|
||||
# パッケージ (package) tab, third of three (期間限定/青輝石/パッケージ).
|
||||
#
|
||||
# KNOWN FUTURE-BREAKAGE RISK (flagged in review, not yet fixable): this is a
|
||||
# single fixed coordinate calibrated only against the CURRENT 3-tab layout,
|
||||
# where 期間限定 (time-limited) is present as the first tab. The reference's
|
||||
# own to_purchase_type branches on exactly this -- it picks a different
|
||||
# パッケージ click x depending on whether 期間限定 is currently showing,
|
||||
# because removing it reflows the remaining tabs. screenshots/gem_shop/2.png
|
||||
# (captured live the same day as this calibration) shows 期間限定's own
|
||||
# on-screen countdown ("終了まであと6日" / "26.06.24〜26.07.29"), meaning
|
||||
# this account's 3-tab layout is itself temporary and due to change around
|
||||
# 2026-07-21 -- not a theoretical edge case. No real screenshot of the
|
||||
# resulting 2-tab (青輝石/パッケージ) layout exists yet to calibrate the
|
||||
# correct alternate x, so a second coordinate has NOT been guessed in here
|
||||
# rather than grounded in a real capture, per this project's own
|
||||
# no-guessing-at-pixel-values discipline.
|
||||
#
|
||||
# This is safe to leave as a known gap rather than a live hazard: if this
|
||||
# coordinate goes stale, _open_package_tab's own status-probe check reads
|
||||
# "unknown" (neither the available-navy nor claimed-red color, since it'd
|
||||
# be looking at the wrong tab's content) and run() aborts cleanly via
|
||||
# _close_gem_shop's verified close WITHOUT ever clicking a purchase button
|
||||
# on whatever tab it actually landed on -- no currency-risk path exists
|
||||
# through this failure mode. Re-calibrate against a real capture once the
|
||||
# tab count actually changes (or the countdown resets on a new campaign
|
||||
# and this account still shows 3 tabs, meaning it wasn't actually an issue).
|
||||
GEM_SHOP_PACKAGE_TAB = (1345, 330)
|
||||
# 毎日無料パッケージ card's own 購入 (purchase) button -- always the
|
||||
# leftmost card, top row, of the package tab (fixed position, matching the
|
||||
# reference's own fixed (385, 479)-style offset rather than a search).
|
||||
GEM_SHOP_FREE_CARD_BUY_BUTTON = (573, 795)
|
||||
# 一般商品 (general products), 3rd item in the left sidebar
|
||||
# (おすすめの商品/限定商品/一般商品/定額商品/青輝石/特別支援). Always
|
||||
# clicked explicitly rather than relying on it already being selected --
|
||||
# even though it was the landing tab in this recalibration, per this
|
||||
# project's own established discipline of never trusting a remembered/
|
||||
# default UI selection before a real click (see memory: BA count/quantity
|
||||
# steppers remember last-used values; the same caution applies to any
|
||||
# selected-tab state here).
|
||||
GEM_SHOP_GENERAL_PRODUCTS_TAB = (196, 376)
|
||||
# デイリー (daily), 1st of 3 sub-tabs (デイリー/ウィークリー/マンスリー)
|
||||
# shown top-right once 一般商品 is open. Also always clicked explicitly for
|
||||
# the same reason as GEM_SHOP_GENERAL_PRODUCTS_TAB above.
|
||||
GEM_SHOP_DAILY_SUBTAB = (1330, 148)
|
||||
# 毎日無料パッケージ card's own 購入 (purchase) button -- the only card
|
||||
# shown under 一般商品 -> デイリー (fixed position, no scrolling/search
|
||||
# needed).
|
||||
GEM_SHOP_FREE_CARD_BUY_BUTTON = (576, 630)
|
||||
|
||||
# The free card's own status bar (just below its artwork, above the price
|
||||
# button) reads a flat, highly distinct color depending on claim state --
|
||||
# confirmed live by claiming the real package and diffing before/after
|
||||
# captures pixel-for-pixel at this exact point:
|
||||
# available ("一日に1回まで購入可能"): flat dark navy ~(41, 65, 90)
|
||||
# claimed ("一日に0回まで購入可能"): flat dark red ~(144, 38, 47)
|
||||
# r vs b relationship alone cleanly separates them (navy: b>r by ~50; red:
|
||||
# r>b by ~100), so no OCR of the "0"/"1" count text is needed -- the
|
||||
# reference itself doesn't OCR this either, it template-matches two whole
|
||||
# separate "purchasable"/"non-purchasable" card images.
|
||||
GEM_SHOP_FREE_CARD_STATUS_PROBE = (420, 710)
|
||||
GEM_SHOP_STATUS_AVAILABLE_RGB = (41, 65, 90)
|
||||
GEM_SHOP_STATUS_CLAIMED_RGB = (144, 38, 47)
|
||||
# see the RECALIBRATED note above for how each value was obtained:
|
||||
# available ("一日にN回まで購入可能"): flat light blue ~(226, 236, 246)
|
||||
# claimed ("一日に0回まで購入可能"): flat dark red ~(190, 56, 66)
|
||||
# The two are far enough apart (light vs dark, blue-family vs red-family)
|
||||
# that no OCR of the "0"/"N" count text is needed -- the reference itself
|
||||
# doesn't OCR this either, it template-matches two whole separate
|
||||
# "purchasable"/"non-purchasable" card images.
|
||||
GEM_SHOP_FREE_CARD_STATUS_PROBE = (410, 485)
|
||||
GEM_SHOP_STATUS_AVAILABLE_RGB = (226, 236, 246)
|
||||
GEM_SHOP_STATUS_CLAIMED_RGB = (190, 56, 66)
|
||||
GEM_SHOP_STATUS_TOLERANCE = 20
|
||||
|
||||
# Generic "is the 青輝石購入 dialog (or a notice/confirm card nested on top
|
||||
# of it) currently showing" check -- navigation.is_on_subscreen/is_modal_open
|
||||
# both proved unreliable here, confirmed live: this dialog overlays directly
|
||||
# on the home screen (keeping its own header bar visible, unlike mailbox/
|
||||
# cafe/shop's own full subscreen headers) so is_on_subscreen's header probe
|
||||
# stays dark in both states; is_modal_open's (960, 200) probe lands on the
|
||||
# dialog's own opaque white card interior rather than a dimmed backdrop, so
|
||||
# it reads bright (not-dark) whether the dialog is open or not -- the exact
|
||||
# same class of default-probe mismatch story_sweep.py/bounty.py already
|
||||
# document for their own wide modals.
|
||||
#
|
||||
# These 4 points sit in the dialog card's plain gutter background (confirmed
|
||||
# identical whichever of the 3 tabs is active, and also identical whether
|
||||
# the base dialog or a nested confirm/notice card is showing on top) and
|
||||
# read a tight, near-white, near-equal-RGB cluster there in every dialog
|
||||
# state captured; the true home screen's own character art was confirmed
|
||||
# NOT to coincidentally match this closely at ALL 4 spread-out points
|
||||
# simultaneously (same multi-point-beats-single-point reasoning as
|
||||
# navigation.is_header_bar_visible, added the same day for an unrelated
|
||||
# cafe bug -- reused here independently since this dialog's false-positive
|
||||
# risk is a different shape, not the header row).
|
||||
GEM_SHOP_DIALOG_PROBES = ((960, 200), (960, 250), (1500, 600), (420, 600))
|
||||
GEM_SHOP_DIALOG_PROBE_MIN_CHANNEL = 220
|
||||
GEM_SHOP_DIALOG_PROBE_MAX_SPREAD = 15
|
||||
|
||||
GEM_SHOP_ICON_RETRIES = 3
|
||||
# Bounds the "press Enter, re-check the free card's status" loop that
|
||||
# advances through the confirm-purchase notice and the "報酬獲得!" reward
|
||||
# banner after clicking 購入 -- confirmed live the reward banner's own entry
|
||||
# animation did not accept input on the first 1-2 presses while its sparkle
|
||||
# animation was still playing, so this needs real patience, not just 1-2
|
||||
# tries.
|
||||
# banner after clicking 購入 -- confirmed live (pre-redesign) the reward
|
||||
# banner's own entry animation did not accept input on the first 1-2
|
||||
# presses while its sparkle animation was still playing, so this needs real
|
||||
# patience, not just 1-2 tries. Not yet re-confirmed against the redesigned
|
||||
# confirm/reward notice specifically -- see gem_shop.py's module docstring.
|
||||
GEM_SHOP_CLAIM_MAX_ATTEMPTS = 6
|
||||
|
||||
# Circle (サークル / guild), ported from module/group.py. Home -> bottom-nav
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -21,55 +21,54 @@ on nik-gpu, which genuinely claimed the account's real free package for the
|
||||
day (0 yen, confirmed +10 AP / +10,000 credits via before/after counter
|
||||
values) -- see scratchpad/gem_shop_*.png for the captures. That exercised
|
||||
the "available -> claim it" path for real, via raw xdotool/scrot rather
|
||||
than this actual module. This module was then live-tested for real
|
||||
against the resulting "already claimed today" state (see plan.md's Gem
|
||||
shop phase) -- the "available -> claim" code path itself is implemented
|
||||
against the same coordinates/logic already confirmed live, but has not
|
||||
yet been exercised by this actual module end-to-end; treat as
|
||||
implemented-but-unverified for that one path until a run happens naturally
|
||||
on a day the package hasn't been claimed yet.
|
||||
than this actual module.
|
||||
|
||||
A `reference-parity-reviewer` pass the same day caught a real bug in the
|
||||
first version of this module: the final dialog-close step verified success
|
||||
via `navigation.is_on_subscreen`, but config.py's own writeup on
|
||||
`GEM_SHOP_DIALOG_PROBES` already documents that probe as blind to this
|
||||
specific dialog (reads the same whether it's open or closed) -- so that
|
||||
check could never fail, making a stuck-open dialog unrecoverable by this
|
||||
module (and, per the same blindness, by `navigation.return_to_home`'s
|
||||
shared fallback too). Fixed by adding `_close_gem_shop`, which verifies
|
||||
against the module's own correctly-calibrated `_dialog_open` instead, with
|
||||
a bounded retry -- reused for both closing paths (the "unknown status"
|
||||
abort and the normal end-of-run close), replacing the original's second,
|
||||
separately-broken blind Escape in the abort path too.
|
||||
RECALIBRATED 2026-07-29 after a real Blue Archive client UI update replaced
|
||||
the old 3-top-tab overlay dialog (期間限定/青輝石/パッケージ) with a real
|
||||
full subscreen (left sidebar + デイリー/ウィークリー/マンスリー sub-tabs
|
||||
under 一般商品). See config.py's "Gem shop daily free package" section for
|
||||
the full writeup of what was re-verified live and what's still an inferred
|
||||
value. The old `_dialog_open`/`_close_gem_shop` custom probes existed only
|
||||
because the pre-redesign overlay dialog was invisible to
|
||||
navigation.is_on_subscreen/is_modal_open; the redesigned screen is a real
|
||||
subscreen, confirmed live, so this module now shares the same
|
||||
is_on_subscreen/return_to_home machinery every other subscreen-based task
|
||||
(mailbox/cafe/shop/lesson) already uses, and those two custom probes are
|
||||
gone.
|
||||
|
||||
A `reference-parity-reviewer` pass on the original version caught a real
|
||||
bug: the final dialog-close step verified success via
|
||||
`navigation.is_on_subscreen`, which the pre-redesign overlay dialog read
|
||||
identically whether open or closed -- so that check could never fail,
|
||||
making a stuck-open dialog unrecoverable. That specific blindness no longer
|
||||
applies post-redesign (is_on_subscreen now genuinely distinguishes this
|
||||
screen from home, confirmed live), which is what made switching to the
|
||||
shared navigation.return_to_home safe here.
|
||||
"""
|
||||
|
||||
def _dialog_open(driver, config):
|
||||
for x, y in config.GEM_SHOP_DIALOG_PROBES:
|
||||
r, g, b = driver.color_at(x, y)
|
||||
spread = max(r, g, b) - min(r, g, b)
|
||||
if not (min(r, g, b) > config.GEM_SHOP_DIALOG_PROBE_MIN_CHANNEL and spread < config.GEM_SHOP_DIALOG_PROBE_MAX_SPREAD):
|
||||
return False
|
||||
return True
|
||||
from ba_auto import navigation
|
||||
|
||||
|
||||
def _open_gem_shop(driver, config):
|
||||
for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1):
|
||||
driver.click(*config.GEM_SHOP_ICON)
|
||||
driver.wait(2)
|
||||
if _dialog_open(driver, config):
|
||||
if navigation.is_on_subscreen(driver):
|
||||
return True
|
||||
print(f"[gem_shop] dialog not detected after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
|
||||
print(f"[gem_shop] subscreen not detected after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
|
||||
return False
|
||||
|
||||
|
||||
def _open_package_tab(driver, config):
|
||||
def _open_daily_general_tab(driver, config):
|
||||
for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1):
|
||||
driver.click(*config.GEM_SHOP_PACKAGE_TAB)
|
||||
driver.click(*config.GEM_SHOP_GENERAL_PRODUCTS_TAB)
|
||||
driver.wait(1)
|
||||
driver.click(*config.GEM_SHOP_DAILY_SUBTAB)
|
||||
driver.wait(1.5)
|
||||
status = _read_free_card_status(driver, config)
|
||||
if status != "unknown":
|
||||
return status
|
||||
print(f"[gem_shop] package tab's free card not recognized after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
|
||||
print(f"[gem_shop] free card status not recognized after click (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
|
||||
return "unknown"
|
||||
|
||||
|
||||
@ -86,24 +85,6 @@ def _read_free_card_status(driver, config):
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _close_gem_shop(driver, config):
|
||||
# Verify with the module's own _dialog_open probe rather than
|
||||
# navigation.is_on_subscreen/is_modal_open -- both are documented above
|
||||
# (see the "Generic... dialog... showing" comment in config.py) to read
|
||||
# the same regardless of whether this specific dialog is open or closed,
|
||||
# so a check against either can never fail and would silently mask a
|
||||
# stuck-open dialog (caught in review before this was live-tested: the
|
||||
# original version of this function used is_on_subscreen for exactly
|
||||
# that dead check).
|
||||
for attempt in range(1, config.GEM_SHOP_ICON_RETRIES + 1):
|
||||
driver.keypress("Escape")
|
||||
driver.wait(1.5)
|
||||
if not _dialog_open(driver, config):
|
||||
return True
|
||||
print(f"[gem_shop] dialog still open after Escape (attempt {attempt}/{config.GEM_SHOP_ICON_RETRIES})")
|
||||
return False
|
||||
|
||||
|
||||
def _claim_free_package(driver, config):
|
||||
driver.click(*config.GEM_SHOP_FREE_CARD_BUY_BUTTON)
|
||||
driver.wait(2)
|
||||
@ -119,14 +100,14 @@ def run(driver, config):
|
||||
driver.focus_game()
|
||||
|
||||
if not _open_gem_shop(driver, config):
|
||||
print("[gem_shop] could not confirm the gem shop dialog opened, aborting without pressing further keys")
|
||||
print("[gem_shop] could not confirm the gem shop screen opened, aborting without pressing further keys")
|
||||
return
|
||||
|
||||
status = _open_package_tab(driver, config)
|
||||
status = _open_daily_general_tab(driver, config)
|
||||
if status == "unknown":
|
||||
print("[gem_shop] could not read the free package card's status, aborting without pressing further keys")
|
||||
if not _close_gem_shop(driver, config):
|
||||
print("[gem_shop] warning: could not confirm the dialog closed")
|
||||
if not navigation.return_to_home(driver):
|
||||
print("[gem_shop] warning: could not confirm return to home")
|
||||
return
|
||||
|
||||
if status == "claimed":
|
||||
@ -138,6 +119,6 @@ def run(driver, config):
|
||||
else:
|
||||
print("[gem_shop] warning: could not confirm the free package was claimed after retries")
|
||||
|
||||
if not _close_gem_shop(driver, config):
|
||||
print("[gem_shop] warning: could not confirm the dialog closed")
|
||||
if not navigation.return_to_home(driver):
|
||||
print("[gem_shop] warning: could not confirm return to home")
|
||||
print("[gem_shop] Done.")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user