- Added `lesson.py` to handle the scheduling of lessons based on affection values. - Integrated OCR functionality to read affection counts from heart badges. - Updated `README.md` to include the new lesson task in the task status section. - Enhanced `config.py` with necessary configurations for the lesson task. - Modified `detector.py` to include a new function for reading heart badge values accurately. - Updated `mapping.md` to reflect the new lesson task implementation. - Adjusted `ba_daily.py` to include the lesson task in the command dispatch. - Updated `plan.md` to document the completion of the lesson task and its testing outcomes. - Modified `setup.sh` to include the lesson task in the run command instructions.
363 lines
20 KiB
Python
363 lines
20 KiB
Python
"""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")
|
|
# A pat that crosses an affection-rank threshold shows a full-screen "絆ラン
|
|
# クアップ!" (Bond Rank Up!) cutscene with no cafe header visible at all --
|
|
# confirmed against screenshots/cafe/student/01-02: navigation.is_on_subscreen's
|
|
# header probe reads (183,220,240) there (r<200, fails) vs (248,249,250) on
|
|
# the normal cafe screen (r>200, passes), so the existing header-brightness
|
|
# check already tells the two apart. Bounds how many Enter presses
|
|
# _dismiss_rank_up_if_shown will try before giving up.
|
|
CAFE_RANK_UP_DISMISS_RETRIES = 5
|
|
|
|
# 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<N>使用して、掃討を
|
|
# <M>回行いますか?" 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"),
|
|
]
|
|
|
|
# Common Shop / Tactical Challenge Shop. Both tabs share the same underlying
|
|
# checkbox-grid-then-bulk-buy UI (the live equivalent of the reference's
|
|
# module/shop/shop_utils.py get_item_position/ensure_choose/buy pattern);
|
|
# see ba_auto/tasks/shop_utils.py for the shared control flow.
|
|
SHOP_ICON = (1155, 1085) # bottom nav "ショップ" icon on the home screen
|
|
SHOP_BACK_BUTTON = (85, 55)
|
|
SHOP_TAB_COMMON = (160, 208) # 通常アイテム tab (credit-point items)
|
|
# 戦術対抗戦 tab (tactical-coin items). The reference reaches this via
|
|
# goto_shop_by_name's OCR swipe-search over the shop-type tab list
|
|
# (module/shop/shop_utils.py) because that list can require scrolling on
|
|
# some accounts/versions. Confirmed live here: this account's tab list is
|
|
# only 7 entries and all fit on screen with no scroll needed, so a fixed
|
|
# click is faithful (there is nothing to search for), not a shortcut around
|
|
# the OCR the reference would otherwise need.
|
|
SHOP_TAB_TACTICAL = (160, 915)
|
|
|
|
# Item grid checkbox top-left-ish click point per (row, col), pixel-scanned
|
|
# live against the shop's real catalog (see plan.md's shop phase). Confirmed
|
|
# identical across both shop tabs -- it's the same shared UI component.
|
|
SHOP_ITEM_COL_X = [972, 1197, 1423, 1649]
|
|
SHOP_ITEM_ROW_Y = [297, 674]
|
|
# The checkbox glyph renders this vivid yellow-green only once checked
|
|
# (plain white/grey otherwise) -- pixel-sampled from a live checked vs.
|
|
# unchecked capture of the same card.
|
|
SHOP_CHECKED_RGB = ((60, 130, 80), (220, 245, 115))
|
|
# Each item's price-digit crop, as an (x1, y1, x2, y2) offset added to that
|
|
# item's own (col_x, row_y). Pixel-scanned and OCR-tested live against all 8
|
|
# configured targets' actual prices (12,500 up to 500,000) -- wide enough
|
|
# for the largest configured price without bleeding into the neighboring
|
|
# column's card.
|
|
SHOP_PRICE_OCR_OFFSET = (48, 166, 145, 195)
|
|
|
|
SHOP_BUY_BUTTON = (1751, 1112)
|
|
SHOP_CANCEL_BUTTON = (1525, 1112)
|
|
# A corner point that both the purchase-confirm dialog and the post-purchase
|
|
# "報酬獲得!" (reward acquired) banner dim away from pure white as they
|
|
# cover the screen -- confirmed live across both, and confirmed to stay pure
|
|
# white with no dialog open (across tab switches and scrolling). Cheaper and
|
|
# more robust than tracking each dialog's own layout individually.
|
|
SHOP_OVERLAY_PROBE = (100, 600)
|
|
SHOP_OVERLAY_IDLE_MIN_CHANNEL = 200
|
|
# Bounds shop_utils.confirm_purchase's "press Enter until idle" loop.
|
|
# Confirmed live: exactly 2 presses clears both the confirm dialog and the
|
|
# reward banner in one purchase; this leaves headroom for any additional
|
|
# one-time popup (e.g. a first-time notice) without spinning forever if the
|
|
# game is ever in a state this project doesn't recognize.
|
|
SHOP_PURCHASE_MAX_ENTER_PRESSES = 6
|
|
|
|
# Top status bar credit-point balance -- present on every screen, not
|
|
# shop-specific. Rect excludes the currency icon on the left, which OCR
|
|
# otherwise misreads as a spurious leading digit (confirmed live).
|
|
CREDIT_BALANCE_OCR_RECT = (1040, 15, 1290, 55)
|
|
# The Tactical Challenge Shop's own coin-balance readout, shown inline above
|
|
# the item grid on that tab specifically (not in the top status bar).
|
|
TACTICAL_COIN_OCR_RECT = (1090, 100, 1290, 150)
|
|
|
|
# (row, col, item name (log/debug only -- identification is by grid
|
|
# position, see shop_utils.py's module docstring for why), expected
|
|
# credit-point price). Buy-list confirmed with the user; all 8 are visible
|
|
# without scrolling.
|
|
COMMON_SHOP_TARGETS = [
|
|
(0, 0, "初級レポート", 12500),
|
|
(0, 1, "中級レポート", 125000),
|
|
(0, 2, "上級レポート", 300000),
|
|
(0, 3, "最上級レポート", 500000),
|
|
(1, 0, "初級強化珠", 10000),
|
|
(1, 1, "中級強化珠", 40000),
|
|
(1, 2, "上級強化珠", 96000),
|
|
(1, 3, "最上級強化珠", 128000),
|
|
]
|
|
|
|
# (row, col, item name, expected tactical-coin price). Buy-list confirmed
|
|
# with the user; both visible without scrolling.
|
|
TACTICAL_SHOP_TARGETS = [
|
|
(0, 0, "初級栄養ドリンク(AP30)", 15),
|
|
(0, 1, "中級栄養ドリンク(AP60)", 30),
|
|
]
|
|
|
|
# Lesson/Schedule (module/lesson.py). This client renders the reference's
|
|
# paged single-region view as a scrollable "Location Select" list of 12
|
|
# named regions instead -- these names are the reference's own
|
|
# lesson_region_name.JP list (core/config/default_config.py), embedded there
|
|
# directly rather than fetched externally, so hardcoding it locally isn't an
|
|
# external-data problem the way the shop price table was. They're used only
|
|
# for logging here, not for OCR identification -- unlike the reference's
|
|
# paged arrows, this list's scroll position is deterministic (see
|
|
# REGION_ROW_Y below), so there's nothing to OCR-locate.
|
|
LESSON_ICON = (314, 1100) # bottom nav "スケジュール" icon on the home screen
|
|
LESSON_BACK_BUTTON = (85, 55) # shared by both the region-list and per-region map screens
|
|
|
|
LESSON_REGION_NAMES = [
|
|
"シャーレオフィス", "シャーレ居住区", "ゲヘナ学園・中央区", "アビドス高等学校",
|
|
"ミレニアム・スタディーエリア", "トリニティ・スクエア", "レッドウインター連邦学園",
|
|
"百鬼夜行中心部", "D.U.シラトリ区", "山海経中央特区", "春葉原", "ワイルドハント総合芸術地区",
|
|
]
|
|
# The region list only ever settles at two scroll positions -- scrolled fully
|
|
# to top (regions 0-5 visible) or fully to bottom (regions 6-11) -- confirmed
|
|
# live: 15 scroll-down clicks always lands on the same bottom state, it
|
|
# doesn't keep scrolling past it. Each row's card is clickable at this
|
|
# center-ish Y regardless of which of the two scroll states is showing.
|
|
LESSON_REGION_ROW_Y = [265, 420, 585, 745, 900, 1060]
|
|
LESSON_REGION_LIST_SCROLL_POINT = (1400, 700)
|
|
LESSON_REGION_LIST_SCROLL_CLICKS = 15
|
|
LESSON_REGION_ROW_X = 1400
|
|
|
|
# Per-region isometric map screen -- opens the "全てのスケジュール" grid
|
|
# modal (this client's rendering of the reference's per-region 3x3
|
|
# get_lesson_each_region_status/get_lesson_relationship_counts grid).
|
|
LESSON_ALL_SCHEDULES_BUTTON = (1770, 1118)
|
|
LESSON_GRID_MODAL_CLOSE_BUTTON = (1710, 211)
|
|
|
|
# Grid modal cell layout: up to 3 columns x 3 rows of location cards, each
|
|
# showing up to 3 student portraits with a heart-shaped affection-count
|
|
# badge at bottom-right. Pixel-scanned live against two different regions'
|
|
# modals (Gehenna Central: 8 cells, Schale Office: 7 cells) -- consistent
|
|
# across both. A region with more than 9 currently-unlocked locations would
|
|
# need scrolling inside this modal, which isn't implemented (not yet seen
|
|
# live on this account; see plan.md).
|
|
LESSON_GRID_COL_X = [270, 786, 1302] # portrait-slot-0 center per column
|
|
LESSON_GRID_ROW_HEADER_Y = [380, 608, 836] # click target to open a cell's info panel
|
|
LESSON_GRID_ROW_PORTRAIT_Y = [486, 713, 940]
|
|
LESSON_GRID_PORTRAIT_STEP_X = 109 # slot 1/2 center = slot 0 center + N * this
|
|
# Badge center sits below-right of each portrait-slot's own center point.
|
|
LESSON_GRID_BADGE_OFFSET = (38, 24)
|
|
LESSON_GRID_BADGE_OCR_HALF_SIZE = (26, 20) # crop half-width/half-height around badge center
|
|
# A green checkmark can appear at top-right of a portrait ALONGSIDE its
|
|
# unchanged heart badge number, not instead of it (confirmed live -- an
|
|
# earlier assumption that "done" always blanks the number was wrong) --
|
|
# this is the only reliable "already done today" signal, so it's checked
|
|
# separately rather than inferred from the badge OCR.
|
|
LESSON_GRID_CHECKMARK_OFFSET = (41, -31)
|
|
LESSON_GRID_CHECKMARK_RGB = ((130, 190, 60), (220, 255, 160))
|
|
LESSON_GRID_CHECKMARK_HALF_SIZE = (14, 13)
|
|
# A masked, digit-only OCR read of the heart badge can still occasionally
|
|
# fuse a stray leading digit from portrait art bleeding into the crop's left
|
|
# edge (confirmed live: "13" -> "413", "18" -> "418", reproducible across
|
|
# every psm mode -- see detector.read_int_on_heart_badge). Real affection
|
|
# values never reach this range in practice, so treat anything this large
|
|
# as contamination and discard it rather than trust it.
|
|
LESSON_GRID_BADGE_MAX_PLAUSIBLE = 99
|
|
|
|
# "保有チケット N/M" readout, top-left of the region-list/map screens.
|
|
LESSON_TICKET_OCR_RECT = (295, 133, 385, 172)
|
|
|
|
# Per-cell info panel ("スケジュール情報"), opened by clicking a grid cell.
|
|
# Its Start button and the post-schedule report's OK button (below) are the
|
|
# same bright-cyan gradient button at nearly the same position -- confirmed
|
|
# by direct pixel sample, sharing one color range.
|
|
LESSON_ACTION_BUTTON_RGB = ((90, 195, 235), (150, 240, 255))
|
|
LESSON_INFO_START_BUTTON = (960, 890)
|
|
LESSON_INFO_CLOSE_BUTTON = (1435, 240)
|
|
|
|
# After clicking Start, a variable sequence of intermediate screens can
|
|
# appear before settling back on the grid modal -- a bond-rank-up full-
|
|
# screen cutscene (confirmed live, see screenshots/lesson/ -- its art is
|
|
# character-dependent, so no fixed color/position reliably identifies it)
|
|
# and/or a "スケジュールレポート" results modal. A transient "run the
|
|
# schedule twice" campaign multiplier was also observed live, doubling how
|
|
# many of these screens appear in a row -- rather than special-case any of
|
|
# this, `lesson.py` presses Enter in a bounded loop and re-checks two fixed
|
|
# markers each round:
|
|
#
|
|
# - the grid modal's own title underline (this exact yellow-gold, confirmed
|
|
# live) is visible ONLY when the grid modal is frontmost and idle -- both
|
|
# the report modal and the cutscene cover it, confirmed live against all
|
|
# three states.
|
|
LESSON_GRID_IDLE_PROBE_RECT = (790, 244, 1130, 252)
|
|
LESSON_GRID_IDLE_RGB = ((235, 220, 70), (255, 250, 130))
|
|
# - the report modal's OK button -- same color as LESSON_ACTION_BUTTON_RGB,
|
|
# its own confirmed fixed position -- clicked directly rather than folded
|
|
# into the blind Enter-press fallback, since we can verify it precisely.
|
|
LESSON_REPORT_OK_BUTTON = (960, 895)
|
|
LESSON_POST_SCHEDULE_MAX_ENTER_PRESSES = 8
|