"""Central configuration for ba-auto-daily, ported from the old ba_dailies.sh.""" import os DISPLAY = ":0" XAUTHORITY = "/run/user/1000/gdm/Xauthority" ENV = {**os.environ, "DISPLAY": DISPLAY, "XAUTHORITY": XAUTHORITY} WINDOW_NAME = "BlueArchive" ASSET_DIR = os.path.expanduser("~/ba_assets") # Runtime working files (probe/detection screenshots); never /tmp, per CLAUDE.md. SCRATCHPAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scratchpad") os.makedirs(SCRATCHPAD_DIR, exist_ok=True) # Refined from (1726, 60): that coordinate sat on the edge of the icon's # hitbox and intermittently missed during live testing. MAILBOX_ICON = (1732, 50) CLAIM_ALL = (1691, 1128) MISSION_ICON = (75, 350) # Background pixel inside the "一括受取" (claim all) button on the Mission # panel: bright yellow (r~250, r-b~180+) when something is claimable, flat # grey (r-b<20) when not. Calibrated live at 1920x1200. MISSION_CLAIM_PROBE = (1600, 1100) CAFE_ICON = (165, 1100) CAFE_ROOM_SWITCH = (190, 160) CAFE_INCOME = (1780, 1105) # Max sparkle-detection attempts per room (hits and misses both count -- # sparkles are on a per-student cooldown, so most checks legitimately find # nothing and the loop keeps polling rather than giving up after one miss). CAFE_MAX_CLICKS_PER_ROOM = 15 CAFE_SPARKLE_TEMPLATE = os.path.join(ASSET_DIR, "cafe_sparkle.png") # Home -> お仕事 (Work hub) -> 任務 (Task) card -> Normal/Hard story region browser. WORK_ICON = (1793, 1138) # Moved up from the original (1370, 450): that point sat close enough to the # 任務 card's bottom edge that a live Phase 10 run missed and landed on the # "総力戦" (Total War) card in the row below instead -- confirmed live via # screenshot, not just a hunch. (1250, 380) sits solidly mid-card, on the # "任務" title text itself, well clear of every edge. TASK_CARD = (1250, 380) REGION_RIGHT_ARROW = (1862, 598) # Pixel-scanline-scanned (not visually estimated -- see plan.md Phase 8's # lesson) from scratchpad/stage_info.png: the "<" chevron's navy-blue pixel # centroid was (66, 597), mirroring REGION_RIGHT_ARROW. Used for the # OCR-driven to_region port (ba_auto/tasks/story_sweep.py) to step backward # when the current region is past the target. REGION_LEFT_ARROW = (66, 598) # Bounds the "read region, click delta, re-check" loop in # ba_auto/tasks/story_sweep.py's _go_to_region. A correct read normally # converges in one round; this just guards against a stuck OCR misread. REGION_NAV_MAX_ATTEMPTS = 8 # Region-number readout on the region browser's left panel (the "Area 30" # card's big digits, below the smaller "Area" label). Rect pixel-scanned from # scratchpad/stage_info.png: the "Area" label occupies roughly y 295-325, the # number itself y 330-372 -- this rect isolates just the digits. Replaces # _go_to_latest_region's "spam the arrow and hope" (see plan.md Phase 9's # retrospective) with task_utils.py::to_region's actual OCR-read-and-click- # the-exact-delta approach. REGION_NUMBER_OCR_RECT = (175, 325, 250, 380) # Stage list panel (right side of the region browser). Scrolling to either # extreme always shows exactly 4 full stage rows, since every region has at # least 5 stages -- scrolling past either end is a harmless no-op (verified # live), so a generous bounded click count is safe. Each stage row's "入場" # (enter) button sits at STAGE_ENTER_X across both scroll extremes; the two # row-position sets below were measured at each extreme. STAGE_LIST_SCROLL_POINT = (1400, 700) # 10 was the original (Phase 9) calibration, but live re-testing during # Phase 10 found it insufficient to reach the opposite extreme when the list # was already scrolled near the other end (it undershot, landing between the # two calibrated row-position sets and producing garbled OCR reads) -- 20 # reliably reached either extreme regardless of starting position. Scrolling # past either end remains a harmless no-op (verified live both phases). STAGE_LIST_SCROLL_CLICKS = 20 STAGE_ENTER_X = 1683 STAGE_ROWS_AT_TOP_Y = (424, 570, 718, 866) STAGE_ROWS_AT_BOTTOM_Y = (483, 630, 778, 926) # Stage-label OCR rect (e.g. "30-1", "30-A"), offset from a row's known # center y above. Pixel-scanned across all eight row positions (both # STAGE_ROWS_AT_TOP_Y and _BOTTOM_Y) in live captures -- x[1030,1150], # y[row_y-40, row_y-4] consistently isolates just the label text line above # the row's star-rating icons. The symmetric row_y+/-40 crop tried first # during calibration included the stars below and reliably broke OCR (empty # or garbled reads) even with a character whitelist -- see plan.md Phase 10. # Replaces _pick_random_stage_row's "scroll to an extreme, grab a random one # of the 4 rows" with a scoped-down port of the reference's # swipe_search_target_str: since this client's stage list only ever has 2 # relevant scroll positions (top/bottom extremes, both already known-good), # full swipe-and-retry generality isn't needed -- just OCR each of the 4 # visible rows at each extreme and match by label text. STAGE_LABEL_OCR_X = (1030, 1150) STAGE_LABEL_OCR_Y_PAD = (40, 4) # 任務情報 (stage info) modal's sweep sub-panel. This modal is wide enough # that navigation.MODAL_DIM_PROBE (960, 200) lands on the modal's own white # card instead of the dimmed backdrop -- use a corner point that's outside # the card in either scroll/region state instead. STAGE_MODAL_PROBE = (1870, 600) # Regular numbered stages (30-1..30-5) render an extra "集中指揮"/"簡易攻略" # tab row and a manual "任務開始" panel below the sweep panel that the # bonus "-A" stage Phase 9 originally calibrated against does not have -- # discovered live during Phase 10 when Phase 9's coordinates (calibrated # only against 30-A) missed the MAX button on 30-3 by ~43px vertically. # These values are pixel-scanned against 30-3's tabbed layout and are what # config.STORY_SWEEP_TARGETS will hit in the common case (sweeping a # regular numbered stage, not the "-A" bonus stage). If a target's stage is # "A", these may be off by the same ~43px the old (untabbed) calibration # used -- not yet re-confirmed against an actual "-A" stage since this fix; # see plan.md Phase 10. SWEEP_MAX_BUTTON = (1620, 550) SWEEP_START_BUTTON = (1400, 710) # The "-" stepper button next to the sweep count: flat grey (240,240,239) # while count is still at its default of 1, vivid orange (255,111,0) once # MAX (or any +) has raised it. Used to verify the MAX click actually landed # instead of trusting a single click blindly, since this gates real AP spend. SWEEP_MINUS_BUTTON_PROBE = (1285, 555) # The modal's own "X" close icon (top-right corner of the white card). # Escape does NOT close this modal (confirmed live: two Escape presses left # it open with focus on the live "任務開始"/start-mission button) -- must # click this explicitly. Pinned via pixel-scanline scan of the glyph's # crossing point, not visual estimation. # # The whole card (not just the sweep sub-panel) is vertically centered on # its own content height rather than anchored at a fixed absolute position # -- confirmed live during Phase 10: the tabbed regular-stage layout (see # SWEEP_MAX_BUTTON above) is taller than the "-A" bonus-stage layout this # was originally calibrated against, and its X button sits ~46px higher # on screen (225 vs the old 271) as a result. This value is re-measured # against the taller, tabbed layout. STAGE_MODAL_CLOSE_BUTTON = (1691, 225) # "+" stepper button, for configured exact (non-"max") sweep counts. Pinned # via color-scan (bright cyan glyph centroid) against 30-3's tabbed layout # (see SWEEP_MAX_BUTTON above) -- unlike SWEEP_MAX_BUTTON/ # SWEEP_MINUS_BUTTON_PROBE this specific button has NOT been live-clicked # yet; confirm it before relying on a non-"max" configured count (see # plan.md Phase 10). SWEEP_PLUS_BUTTON = (1520, 550) # Clicking 掃討開始 (start sweep) always raises an "AP使用して、掃討を # 回行いますか?" usage-confirmation dialog before the sweep actually # runs -- discovered live during Phase 10; the previous design had no # handling for this dialog at all, which is what a crude early placeholder # probe was misreading as "inadequate_ap" on every sweep, successful or not. # # If AP is too low for even one sweep (confirmed live by deliberately # emptying the count via MAX/"+" at low AP), a dialog that looks the same # and sits at the *same* OK-button position appears instead, but titled # "AP購入" (real-currency AP purchase) with a gold/yellow OK button instead # of cyan. The two are told apart by that color, not by position. SWEEP_CONFIRM_BUTTON = (1150, 810) SWEEP_CONFIRM_CANCEL_BUTTON = (770, 810) SWEEP_CONFIRM_CYAN = ((90, 190, 230), (200, 240, 256)) SWEEP_CONFIRM_GOLD = ((200, 200, 50), (256, 256, 150)) # Region spanning every button this task clicks through after 掃討開始: the # AP-usage-confirm OK above, and the "掃討完了" (sweep complete) results # screen's "SKIP" (first, skips the reward-reveal animation) and final "OK" # (after full reward totals appear) buttons. SKIP and the final OK share # SWEEP_CONFIRM_CYAN's color but sit ~120px apart vertically, so this task # finds whichever one is showing by color within this region instead of # hardcoding each dialog's exact Y position. SWEEP_RESULT_BUTTON_REGION = (700, 700, 1300, 1050) # (region, stage, count) targets to sweep, mirroring the reference's # unfinished_normal_tasks shape (module/explore_tasks/sweep_task.py). `stage` # is 1-5, or the string "A" for the bonus stage that only exists when # region % 3 == 0. `count` is a positive int (uses the "+" stepper) or the # literal string "max" (uses the in-game MAX button). # # PLACEHOLDER -- region 1 stage 1 is unlikely to be what you actually want # swept (it may not even be 3-starred/cleared on this account yet). Edit # this list with your own already-cleared stage(s) before running # story_sweep for real. STORY_SWEEP_TARGETS = [ (1, 1, "max"), ]