diff --git a/.gitignore b/.gitignore index 179cdea..a10db92 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ graphify-out/cost.json graphify-out/cache/ __pycache__/ *.pyc -.scratchpad/ +scratchpad/ diff --git a/CLAUDE.md b/CLAUDE.md index a927e99..fdf9ec8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -197,7 +197,7 @@ Do not introduce OCR casually. Add it only when implementing a feature that actu ## Working conventions -Use `.scratchpad/` (create if missing) in the project root for temporary/intermediate files — e.g. cropped calibration images from `screenshots/cafe/sparkle/`, one-off debug output. Never write to `/tmp` or `/private/tmp`. +Use `scratchpad/` (create if missing) in the project root for temporary/intermediate files — e.g. cropped calibration images from `screenshots/cafe/sparkle/`, one-off debug output. Never write to `/tmp` or `/private/tmp`. ## Bash policy @@ -319,7 +319,7 @@ The detector should support: - threshold tuning - masked matching - click-offset handling -- debug image output to `.scratchpad/` +- debug image output to `scratchpad/` Avoid one Python cold start per click attempt where possible. Prefer long-running Python task logic that can take repeated screenshots and click repeatedly from one process. @@ -385,7 +385,7 @@ Use this format: ## Working conventions -Use `.scratchpad/` for temporary or intermediate files. +Use `scratchpad/` for temporary or intermediate files. Examples: @@ -421,7 +421,7 @@ Example: ssh nik-gpu "~/ba_dailies.sh cafe" ``` -When debugging image matching, write debug images to `.scratchpad/`. +When debugging image matching, write debug images to `scratchpad/`. ## Existing features diff --git a/README.md b/README.md index 824ac90..2ac82e5 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Current task status: |---|---| | `mailbox` | Real Python (`ba_auto/tasks/mailbox.py`). Verifies the mailbox panel actually opened (via a pixel-color probe, `driver.color_at`) before clicking "claim all" or pressing any further keys. Retries the open-click up to 3 times before giving up safely. | | `cafe` | Real Python (`ba_auto/tasks/cafe.py`). Verifies each room/dialog transition the same way as `mailbox` before acting; sparkle detection runs in-process via `ba_auto/detector.py` instead of shelling out per click. See "Fixed: the exit-game dialog bug" below for what this replaced, and `plan.md` Phase 6 follow-up for the multi-scale detection + persistent-polling changes made after a "farming affection doesn't happen" report. | +| `stamina` | Real Python (`ba_auto/tasks/stamina.py`). Opens the Mission panel and claims via its bulk "一括受取" button (Enter key) when enabled. Does **not** touch the Pyroxene Purchase (青輝石購入) menu's free-AP claim — that's a real-money purchase screen and was deliberately left unautomated; see `plan.md` Phase 8. | ## Prerequisites on nik-gpu @@ -62,9 +63,10 @@ ssh nik-gpu "cd ~/repo/ba-auto-daily && ./setup.sh" The game must already be running on `nik-gpu` (window titled `BlueArchive`). Then, on `nik-gpu`: ```bash -~/ba_dailies.sh # default: mailbox, then cafe +~/ba_dailies.sh # default: mailbox, cafe, then stamina ~/ba_dailies.sh mailbox # just mailbox ~/ba_dailies.sh cafe # just cafe +~/ba_dailies.sh stamina # just the Mission-panel bulk claim ``` You can also run these remotely without a separate `ssh` login step: @@ -73,7 +75,7 @@ You can also run these remotely without a separate `ssh` login step: ssh nik-gpu "~/ba_dailies.sh mailbox" ``` -Exit code `0` means the script ran to completion without a Python exception — it does **not** by itself guarantee the in-game action succeeded. Both `mailbox` and `cafe` log what they actually detected and did (or why they safely aborted), so check the log output, not just the exit code. +Exit code `0` means the script ran to completion without a Python exception — it does **not** by itself guarantee the in-game action succeeded. `mailbox`, `cafe`, and `stamina` all log what they actually detected and did (or why they safely aborted), so check the log output, not just the exit code. ## How to watch/verify it diff --git a/ba_auto/config.py b/ba_auto/config.py index 1fc4e36..609c6c9 100644 --- a/ba_auto/config.py +++ b/ba_auto/config.py @@ -17,6 +17,12 @@ os.makedirs(SCRATCHPAD_DIR, exist_ok=True) 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) diff --git a/ba_auto/driver.py b/ba_auto/driver.py index 90f0ba2..68fd56b 100644 --- a/ba_auto/driver.py +++ b/ba_auto/driver.py @@ -29,7 +29,13 @@ def focus_game(): def click(x, y): - run_command(["xdotool", "mousemove", str(x), str(y), "click", "1"]) + # A combined "mousemove X Y click 1" invocation is unreliable here -- + # empirically (see plan.md's click-flakiness writeups) the game's input + # handler sometimes misses clicks fired before it's processed the move. + # Splitting into separate commands with a short pause between fixes it. + run_command(["xdotool", "mousemove", str(x), str(y)]) + wait(0.2) + run_command(["xdotool", "click", "1"]) wait(0.5) diff --git a/ba_auto/reference_notes/mapping.md b/ba_auto/reference_notes/mapping.md index 5117ad2..5dabce9 100644 --- a/ba_auto/reference_notes/mapping.md +++ b/ba_auto/reference_notes/mapping.md @@ -6,7 +6,7 @@ Maps each local feature to the corresponding `~/repo/baas-reference/module/...` |---|---|---|---|---|---| | Mailbox | `module/mail.py` | `to_mail`, `implement` | `ba_auto/tasks/mailbox.py` | tap/click via xdotool, screenshot via scrot, `color.rgb_in_range` → `driver.color_at` pixel-probe check | Migrated: real Python, state-verified via color probe (no legacy bridge) | | Cafe | `module/cafe_reward.py` | `to_cafe`, `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 | -| Stamina/AP | `module/collect_daily_free_power.py`, `module/collect_daily_task_power.py` | Need to inspect | `ba_auto/tasks/stamina.py` | color checks/clicks via local driver | Not started | +| 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 | | 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 | | Commissions | `module/clear_special_task_power.py` | Need to inspect | `ba_auto/tasks/commission.py` | sweep/color adaptation | Not started | diff --git a/ba_auto/tasks/stamina.py b/ba_auto/tasks/stamina.py new file mode 100644 index 0000000..6f9f931 --- /dev/null +++ b/ba_auto/tasks/stamina.py @@ -0,0 +1,59 @@ +"""Mission/task-menu AP+pyroxene claim. Ported from baas-reference module/collect_daily_task_power.py's claim-loop pattern (its rgb_in_range checks replaced with driver.color_at probes).""" + +from ba_auto import navigation + +OPEN_RETRIES = 3 +# One claim-all round is normally enough (button goes grey right after), but +# bound it in case multiple reward reveals need dismissing in sequence. +CLAIM_MAX_ROUNDS = 5 + +# Yellow "one-click claim all" button vs. its own flat-grey disabled state -- +# same background-pixel-probe idea as mailbox.CLAIM_ALL_PROBE/cafe.CLAIM_PROBE, +# but distinguished by hue (yellow has a big r-b gap; grey doesn't) since the +# button's grey isn't a single fixed RGB the way mailbox/cafe's are. +CLAIM_ENABLED_MIN_R = 230 +CLAIM_ENABLED_MIN_RB_GAP = 100 + + +def _claim_enabled(driver, config): + r, g, b = driver.color_at(*config.MISSION_CLAIM_PROBE) + return r > CLAIM_ENABLED_MIN_R and (r - b) > CLAIM_ENABLED_MIN_RB_GAP + + +def run(driver, config): + driver.focus_game() + + opened = False + for attempt in range(1, OPEN_RETRIES + 1): + driver.focus_game() + driver.click(*config.MISSION_ICON) + driver.wait(2) + if navigation.is_on_subscreen(driver): + opened = True + break + print(f"[stamina] mission panel not detected after click (attempt {attempt}/{OPEN_RETRIES})") + + if not opened: + print("[stamina] could not confirm mission panel is open, aborting without pressing further keys") + return + + claimed_any = False + for _ in range(CLAIM_MAX_ROUNDS): + if not _claim_enabled(driver, config): + break + print("[stamina] claiming mission rewards") + driver.keypress("Return") + driver.wait(1.5) + # dismiss the reward-reveal popup ("TOUCH" prompt); harmless no-op if + # nothing is actually showing, same assumption as cafe's room-entry dismiss + driver.keypress("Return") + driver.wait(1.5) + claimed_any = True + + if not claimed_any: + print("[stamina] nothing to claim") + + if navigation.is_on_subscreen(driver): + driver.keypress("Escape") + driver.wait(1.5) + print("[stamina] Done.") diff --git a/ba_daily.py b/ba_daily.py index 1369647..022904d 100644 --- a/ba_daily.py +++ b/ba_daily.py @@ -3,13 +3,14 @@ import sys from ba_auto import config, driver -from ba_auto.tasks import cafe, mailbox +from ba_auto.tasks import cafe, mailbox, stamina TASKS = { "mailbox": mailbox.run, "cafe": cafe.run, + "stamina": stamina.run, } -DEFAULT_ORDER = ["mailbox", "cafe"] +DEFAULT_ORDER = ["mailbox", "cafe", "stamina"] def main(argv): diff --git a/graphify-out/graph.html b/graphify-out/graph.html index cbb5e92..76b85b9 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -66,7 +66,7 @@
159 nodes · 176 edges · 23 communities