- Updated `mapping.md` to reflect completed migration and testing status for event sweep and arena tasks. - Improved `arena.py` to ensure a return to home screen before opening tactical challenges, preventing navigation errors. - Enhanced `event_sweep.py` with robust handling for badge carousel navigation, including direct pagination dot clicks and extended wait times for cold-start scenarios. - Implemented Japanese OCR support for finished event detection in `event_sweep.py`, adding a definitive check to avoid false positives on stale event pages. - Adjusted retry logic and timeouts in `event_sweep.py` to accommodate longer loading times and ensure accurate stage row detection. - Updated `setup.sh` to check for the presence of the Japanese OCR language pack, providing installation instructions if missing. - Documented live testing results and fixes in `plan.md`, confirming successful sweeps and addressing previously reported bugs.
31 KiB
CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
What this project is
This repository is a personal Blue Archive JP daily-automation project.
It controls the real Blue Archive PC/Steam/Proton client running on a Linux machine named nik-gpu. The development machine is a MacBook named nik-macbookair.
The original goal is a backend port, not a rewrite. The reference project (~/repo/baas-reference/) only works against an Android emulator via ADB/uiautomator2, typically run on a Windows PC. This project's job is to make that same automation logic work against the real JP client running under Proton on Linux instead — porting the reference's decision logic, navigation, and state-detection approach, including its use of OCR, as faithfully as practical, and replacing only the parts that are genuinely Android-specific with local desktop equivalents.
Android-specific parts include:
- ADB input
- uiautomator2 taps
- Android UI-object-tree queries
- emulator-specific assumptions
Local desktop equivalents include:
xdotoolscrot- OpenCV
- OCR against screenshots
- local image/color/template detection
Inventing a different, simpler approach to avoid porting a piece of reference logic is a deviation from the project goal, not a shortcut toward it.
The automation backend is local desktop control:
xdotoolfor mouse/keyboard/window controlscrotfor screenshots- Python/OpenCV for image matching and color/template detection
- OCR, using Tesseract or PaddleOCR, wherever the reference implementation uses OCR for a feature
- no Android emulator control
- no ADB
- no uiautomator2
OCR policy
OCR is not an Android-specific concern. The reference's OCR-driven region, stage-name, and currency matching runs against a screenshot and can be ported to this backend using scrot captures. Only the reference's input mechanism is Android-specific.
Two features, Stamina/AP mission claim and Normal/Hard story AP sweep, were originally built without OCR by substituting ad hoc pixel probes, fixed coordinates, or randomized selection for the reference's OCR-driven navigation. For story sweep, that substitution produced more real bugs during live testing than porting the reference's actual approach would have:
- a wrong modal-open probe
- an unverified button click that silently under-spent AP
- a modal that does not close on Escape
- a latent hazard where a mistimed keypress could have started a real battle
See plan.md Phase 9's retrospective for the full writeup.
That issue has since been fixed: story_sweep.py now ports the reference's actual OCR-based region/stage targeting. See plan.md Phase 10. The retrospective stays here as the concrete, lived reason for the OCR policy, not as a description of the current state of that task.
Going forward: when a reference feature's control flow depends on OCR, set up OCR and port that logic. Do not invent a non-OCR workaround just to avoid the setup cost. Only skip OCR for a specific step if the reference itself does not use OCR there.
The reference project is located at:
~/repo/baas-reference/
That repository contains the full Blue Archive Auto Script implementation. It should be treated as the behavioral reference for this project.
Core architecture decision
This project must be Python-first.
Do not implement new Blue Archive automation logic in ba_dailies.sh.
ba_dailies.sh is only a thin runtime/deployment launcher for convenience on nik-gpu.
All feature logic must live in Python.
The intended structure is:
ba-auto-daily/
├── ba_dailies.sh
├── ba_daily.py
├── ba_auto/
│ ├── __init__.py
│ ├── driver.py
│ ├── detector.py
│ ├── navigation.py
│ ├── config.py
│ ├── tasks/
│ │ ├── __init__.py
│ │ ├── mailbox.py
│ │ ├── cafe.py
│ │ ├── stamina.py
│ │ ├── group.py
│ │ └── ...
│ └── reference_notes/
│ └── mapping.md
├── assets/
├── screenshots/
├── scratchpad/
├── setup.sh
├── plan.md
└── CLAUDE.md
scripts/ previously held one-off Bash/Python helpers. It was deleted once mailbox and cafe migrated off it. Do not recreate scripts/ as a place to stash feature logic.
The exact layout can evolve, but the architectural rule should not change:
- Bash launches Python.
- Python owns automation logic.
- The reference repository guides feature behavior.
- Local driver primitives adapt that behavior to the PC/Steam/Proton setup.
Reference-first rule
Before implementing any new feature, inspect the matching reference implementation in:
~/repo/baas-reference/module/
Do not start by inventing a Bash click sequence.
For every feature, first identify:
- Which reference file implements it.
- Which class/function contains the main control flow.
- What the reference uses for state detection.
- What the reference uses for retries/failure handling.
- Which parts depend on Android/uiautomator2 and must be replaced.
- Which parts can be ported directly as Python control flow.
- Which local driver primitives are missing.
Then implement the feature in Python under:
ba_auto/tasks/
Reference repository usage
The reference repository is read-only.
Allowed
- read reference modules
- inspect control flow
- reuse architecture ideas
- reuse retry/state-machine structure
- reuse task decomposition ideas
- reuse constants/config concepts when appropriate
- write local notes describing how a reference module maps to this project
Not allowed
- edit files in
~/repo/baas-reference/ - reimplement a reference feature from scratch in Bash
- create a local solution that ignores the reference flow when a reference implementation already exists
- substitute a pixel probe, fixed coordinate, or randomized shortcut for reference logic that uses OCR, just to avoid setting up OCR
Two-machine architecture
Development happens on:
nik-macbookair
Runtime happens on:
nik-gpu
The Blue Archive client and X display live on nik-gpu.
Assume:
DISPLAY=:0
GDM XAUTHORITY under /run/user/1000
The game runs under Steam/Proton on the Linux desktop.
There is no reliable local execution path on macOS. Anything that interacts with the game must be deployed to nik-gpu.
Deployment model
During iteration, files are pushed from nik-macbookair to nik-gpu.
Typical paths on nik-gpu:
~/ba_dailies.sh
~/ba_daily.py
~/ba_auto/
~/ba_assets/
~/.venvs/ba-auto-daily/
The current setup may still contain older paths such as:
~/ba_scripts/detect_and_click.py
When refactoring, prefer consolidating Python code into ba_auto/.
setup.sh should bootstrap or update the runtime layout on nik-gpu.
For a fresh checkout on nik-gpu, run from the repo root:
./setup.sh
After initial setup, individual changes may be pushed with scp or rsync.
Prefer syncing the project from the repository root:
rsync -av \
--exclude='.git/' \
--exclude='__pycache__/' \
--exclude='*.pyc' \
--exclude='.claude/settings.local.json' \
--exclude='graphify-out/' \
--exclude='screenshots/' \
--exclude='scratchpad/' \
./ nik-gpu:~/repo/ba-auto-daily/
Do not sync Claude Code internal temporary folders.
Runtime dependencies on nik-gpu
These are host-level dependencies. Confirm they exist before assuming a bug is in the project code.
Required now:
xdotoolscrotpython3
Required Python packages:
opencv-pythonoropencv-python-headlessnumpypytesseract
Expected venv:
~/.venvs/ba-auto-daily/bin/python3
Quick check:
ssh nik-gpu "which xdotool scrot tesseract && ~/.venvs/ba-auto-daily/bin/python3 -c 'import cv2, numpy, pytesseract; print(cv2.__version__)'"
OCR engine dependency is set up as of Phase 10:
- Tesseract via the
tesseract-ocrapt package pytesseractin the venv
Installing tesseract-ocr needs interactive sudo, so setup.sh should check for it but should not assume it can install it automatically.
As of Phase 14's live-debugging (see plan.md's Phase 14 follow-up #5), the tesseract-ocr-jpn language pack is also needed, but only for event_sweep.py's _is_finished_event_page check (Japanese free-text OCR to detect a finished event's own "イベント期間が終了しました" page) -- every other task's OCR only ever needed the base eng data (digit/whitelisted-character reads), so this is a soft dependency specific to that one task, not a project-wide requirement. setup.sh checks for it via tesseract --list-langs and prints the install command (sudo apt-get install -y tesseract-ocr-jpn) if missing, but does not hard-fail setup over it, same interactive-sudo caveat as the base package.
ba_auto/detector.py's read_text() and read_int() wrap OCR for occasional single-crop reads. See story_sweep.py for the first real usage. read_text() takes a lang parameter (default "eng") for this.
Bash policy
ba_dailies.sh should be a thin launcher only.
Preferred shape:
#!/usr/bin/env bash
set -euo pipefail
VENV_PYTHON="${VENV_PYTHON:-$HOME/.venvs/ba-auto-daily/bin/python3}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "$VENV_PYTHON" "$SCRIPT_DIR/ba_daily.py" "$@"
Acceptable Bash responsibilities:
- choose Python binary
- set environment variables
- call Python entry point
- provide compatibility with old command names
- fail early if the Python entry point is missing
Not acceptable in Bash:
- feature state machines
- long click sequences
- OpenCV logic
- OCR logic
- retry loops for game states
- feature-specific navigation
- shop/sweep/battle logic
- new
do_<feature>game automation functions
If an existing Bash function exists, migrate it to Python rather than extending it.
Python entry point
The intended Python CLI entry point is:
ba_daily.py
It should support commands such as:
./ba_dailies.sh
./ba_dailies.sh mailbox
./ba_dailies.sh cafe
./ba_dailies.sh stamina
./ba_dailies.sh story_sweep
./ba_dailies.sh group
No argument should run the default daily flow.
Current default flow in ba_daily.py's DEFAULT_ORDER:
- focus game
- mailbox
- cafe
- stamina
- future daily tasks
story_sweep spends AP rather than reclaiming something free, so it is deliberately excluded from the default flow. It must be invoked explicitly.
The CLI should dispatch into task modules under:
ba_auto/tasks/
Driver layer
Create and maintain a driver layer in:
ba_auto/driver.py
The driver layer should wrap the PC/Steam/Proton backend.
It should provide reusable primitives such as:
focus_game()
screenshot()
click(x, y)
double_click(x, y)
drag(start, end, duration)
swipe(start, end, duration)
keypress(key)
wait(seconds)
wait_until(...)
color_at(...)
region_average_color(...)
template_match(...)
find_and_click_template(...)
Feature modules should not directly shell out to xdotool or scrot unless a driver primitive is missing and is being added.
Prefer:
driver.click(x, y)
over:
subprocess.run(["xdotool", "click", "1"])
This keeps the project close to the reference architecture while replacing only the backend-control layer.
Detector layer
Image matching, color matching, and OCR wrappers should live in:
ba_auto/detector.py
or in clearly named helper classes/functions.
The cafe sparkle-matching logic formerly in scripts/detect_and_click.py has been migrated into ba_auto/detector.py as find_cafe_sparkle(). It should be called in-process rather than as a per-click subprocess.
The detector should support:
- screenshot input
- template matching
- threshold tuning
- masked matching
- click-offset handling
- OCR crop reads
- 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.
Navigation layer
Common navigation should live in:
ba_auto/navigation.py
Use this for shared flows such as:
- returning home
- opening main menu
- opening mailbox
- opening cafe
- opening shop
- opening lesson/schedule
- closing popups
- generic back/escape handling
- waiting for known UI states
Do not duplicate navigation click sequences inside every task if they can be shared.
Task modules
Each feature should have a task module:
ba_auto/tasks/<feature>.py
Examples:
ba_auto/tasks/mailbox.py
ba_auto/tasks/cafe.py
ba_auto/tasks/stamina.py
ba_auto/tasks/story_sweep.py
ba_auto/tasks/group.py
Each task module should expose a clear function such as:
run(driver, config)
or:
run_mailbox(driver, config)
Keep task files feature-focused.
Reference mapping notes
Maintain a mapping file at:
ba_auto/reference_notes/mapping.md
Before or during implementation of a feature, update the mapping.
Use this format:
| Local feature | Reference file | Reference functions/classes | Local file | Backend replacements | Status |
|---|---|---|---|---|---|
| Cafe | `module/cafe.py` or relevant file | `...` | `ba_auto/tasks/cafe.py` | uiautomator2 tap -> xdotool click, screenshot -> scrot/OpenCV | In progress |
Working conventions
Always work from the repository root:
cd ~/repo/ba-auto-daily
Use scratchpad/ in the project root for all temporary, generated, diagnostic, calibration, and investigation files.
Examples:
- cropped calibration images
- debug screenshots
- annotated match results
- temporary Python probes
- temporary investigation notes
- one-off image-analysis scripts
- temporary OCR experiments
- screenshots copied back from
nik-gpufor analysis
Create it if missing:
mkdir -p scratchpad
Do not use Claude Code's internal temporary directories such as:
/private/tmp/claude-*
/tmp/claude-*
Do not run probes from /private/tmp/claude-* or write temporary scripts there. These paths are outside the repository and often trigger extra permission prompts, interrupting the development flow.
When a temporary Python probe is needed, write it into scratchpad/ first, then run it from the repository root.
Preferred pattern:
cat > scratchpad/probe_image.py <<'PY'
from PIL import Image
img = Image.open("scratchpad/shop_probe_04_checked.png")
print(img.size)
points = [
("checkbox_checked_center", (972, 297)),
("checkbox_checked_bg", (975, 300)),
("card_border_left", (940, 380)),
("card_border_unselected", (1165, 380)),
("buy_button", (1751, 1112)),
("cancel_button", (1525, 1112)),
]
for name, xy in points:
print(name, xy, img.getpixel(xy))
PY
python3 scratchpad/probe_image.py
Avoid this pattern:
cd /private/tmp/claude-*/scratchpad && python3 -c "..."
Also avoid large multiline python3 -c "..." commands when they contain comments or complex quoting. Prefer a temporary script under scratchpad/ because it is easier to inspect, rerun, and keep within workspace permissions.
screenshots/ contains human reference captures. They are useful for calibration and documentation, but they are not necessarily automated test fixtures.
assets/ contains local template images. These should be captured from the local game setup where possible.
Temporary probe policy
Temporary probes are allowed, but they must be workspace-local.
For non-trivial investigation, use:
scratchpad/<short_descriptive_name>.py
Examples:
scratchpad/probe_shop_checkbox.py
scratchpad/probe_cafe_income.py
scratchpad/probe_story_modal.py
scratchpad/probe_ocr_region.py
A temporary probe should:
- run from the repository root
- read inputs from
scratchpad/,screenshots/, orassets/ - write outputs to
scratchpad/ - avoid
/tmp,/private/tmp, and Claude Code internal paths - avoid relying on absolute
/private/tmp/claude-*paths - be deletable once the investigation is complete
Prefer:
python3 scratchpad/probe_shop_checkbox.py
Do not prefer:
python3 -c "large multiline script..."
For very small one-liners, python3 -c is acceptable only when it does not require changing directories into /private/tmp/claude-*.
Testing and checks
Because the game only runs on nik-gpu, local macOS testing is limited.
Before deploying, run static/syntax checks locally:
bash -n ba_dailies.sh
python3 -m py_compile ba_daily.py
python3 -m py_compile ba_auto/*.py
python3 -m py_compile ba_auto/tasks/*.py
For temporary investigation scripts, use:
python3 scratchpad/<script_name>.py
Do not run temporary investigation scripts from Claude Code's internal /private/tmp/claude-* workspace.
On nik-gpu, run real integration tests against the live game.
Example:
ssh nik-gpu "~/repo/ba-auto-daily/ba_dailies.sh cafe"
When debugging image matching, write debug images to:
scratchpad/
When debugging on nik-gpu, copy relevant screenshots or debug images back into the local project scratchpad/ if they need local analysis.
Existing features
Current project state: mailbox, cafe, stamina, story_sweep, shop_common, shop_tactical, lesson, and arena are all migrated to real Python. No Bash feature logic remains.
ba_dailies.shis a thin launcher that execsba_daily.pyba_daily.pydispatchesmailbox,cafe,stamina,story_sweep,shop_common,shop_tactical,lesson,arena, and default flow toba_auto/tasks/- default flow is
mailbox,cafe,stamina story_sweep,shop_common,shop_tactical,lesson, andarenaare opt-in only since they spend AP/credits/tactical coin/lesson tickets/an arena ticket rather than reclaiming something freeba_auto/tasks/mailbox.pyandba_auto/tasks/cafe.pyclick withba_auto/driver.pyprimitives and verify state withdriver.color_atandba_auto/navigation.py- mailbox and cafe were ported from the reference patterns around
module/mail.pyandmodule/cafe_reward.py - no legacy bridge remains
ba_auto/tasks/stamina.pyclaims the Mission panel's bulk一括受取button- see
plan.mdPhase 8 for stamina details ba_auto/tasks/story_sweep.pysweeps a config-driven list of exact(region, stage, count)targets fromconfig.STORY_SWEEP_TARGETS, plus one daily-rotating target (config.STORY_SWEEP_ROTATION_*) that cycles through a fixed region's stages one per day- story sweep navigates via OCR, using region-number readout and stage-label matching, rather than random selection
- see
plan.mdPhase 10 for the OCR-based story sweep port, and its "Phase 10 follow-up" for the rotation-target fix ba_auto/tasks/shop_common.pyandba_auto/tasks/shop_tactical.pyshare a checkbox-grid-then-bulk-buy flow (ba_auto/tasks/shop_utils.py) against config-driven(row, col, name, expected_price)targets (config.COMMON_SHOP_TARGETS/config.TACTICAL_SHOP_TARGETS)- item identification is by fixed grid position, not per-item OCR — the reference's own
get_item_positionindexes an external static price table this repo doesn't have, so a locally pixel-scanned position table is the faithful port, not an OCR-avoidance shortcut; price-digit OCR is layered on top as an extra catalog-drift safety check the reference doesn't even do per-item - both shops were live-tested with real purchases (see
plan.mdPhase 11), which also surfaced a real, previously-unknown per-refresh-cycle purchase cap on these items (not shown as a visible counter) — the task correctly detected the now-unselectable items and safely declined rather than misfiring ba_auto/tasks/lesson.pysweeps every unlocked region's schedule grid (a scrollable list of 12 named regions, each opening a grid modal of up to 9 location cards), picking the highest-affection available lesson each time until lesson tickets or lessons run out- per-cell affection is read via
detector.read_int_on_heart_badge, a dedicated OCR path for the pink/magenta heart-shaped badge — the project's normal grayscale-threshold OCR (read_int) misreads it, because the badge's own outline stroke survives the same threshold as the digit glyph; a "done today" portrait keeps its number and gets a green checkmark added alongside it rather than losing the number, so done-ness is checked via that checkmark's color, not inferred from a failed OCR read - lesson was live-tested with real tickets spent (see
plan.mdPhase 12), which surfaced two real bugs from that assumption gap plus an OCR contamination issue — both fixed; see Phase 12 for the full writeup - a later regression broke
LESSON_TICKET_OCR_RECTentirely (a stray katakana fragment at the crop's left edge made tesseract drop the leading digit,"7/7"reading as"/7", aborting every run) — fixed by tightening the rect; re-validated live with 7 real tickets spent and correct re-reads after every schedule. Seeplan.md's "Phase 12 follow-up" - a second regression then surfaced: clicking the schedule icon doesn't always land on the Location Select list — the game can resume directly on whichever region's per-region isometric map was last open (a previous run's Ctrl-C interruption left it stuck there), which broke navigation for every region identically since the list's scroll/row-click logic doesn't apply to that screen. Fixed via
lesson._ensure_location_select_list, which detects the per-region map's own "all schedules" button already showing and returns via the back button before sweeping. Validated by deliberately reproducing the stuck state and confirming recovery with a real ticket spend. Seeplan.md's "Phase 12 follow-up #2" ba_auto/tasks/arena.pyfights exactly one ranked Tactical Challenge (Arena) battle per invocation (not "spend every ticket" — the reference itself only fights one per call, relying on its own background scheduler for pacing, which this project has no equivalent for), then collects both reward slots- Tactical Challenge is reached via a card inside the お仕事 (Work) hub, not a bottom-nav icon; the reference's separate opponent-info and formation-edit screens are merged into one modal here with a live ticket-count preview confirming the real fight-commit click
- arena was live-tested for real across all 5 of the account's daily tickets (2 WIN, 1 LOSE, 2 spent debugging), which surfaced three real bugs: a level-OCR crop too small for tesseract despite looking legible to the eye, level text being bright-on-dark unlike every other OCR read in this project (fixed via a new
detector.read_int_white_on_dark), and — most importantly — the post-fight WIN/LOSE result modal proving undetectable by precisely locating its own confirm button (WIN and LOSE are different heights; widening the search region to cover both then caught stray cyan-ish pixels in the opponent list's own portrait art, false-positive-clicking into an unrelated opponent's info modal). Fixed by abandoning per-button color detection for a bounded blind-Enter-press loop (matchinglesson.py's own_run_one_schedulepattern), gated by a hard safety check against the one modal where Enter is genuinely dangerous — the opponent-info modal's own attack-formation button is also Enter-bound and spends a real ticket. Seeplan.mdPhase 13 for the full writeup ba_auto/detector.pyhasfind_cafe_sparkle(), the sparkle template-match ported in-process from the now-deletedscripts/detect_and_click.py, andfind_template()/template_visible(), a generalized named-template matcher built for arena but ultimately unused there — state detection stayed OCR/color-probe-driven throughout, like every other taskscripts/ba_dailies_legacy.shandscripts/detect_and_click.pyhave been deleted- the
scripts/directory itself no longer exists ba_auto/driver.pyprimitives are wired into all migrated task modules- existing driver primitives include
run_command,focus_game,click,move_mouse,scroll,keypress,screenshot,wait, andcolor_at ba_auto/navigation.pyhas shared state probes used across tasks:is_on_subscreenis_modal_openwait_for_state()
wait_for_state() is a scoped port of the reference's core/picture.py::co_detect: watch for any of several named states, react to known non-terminal ones, and stop on a recognized terminal one.
story_sweep.py additionally has its own local modal probes and close logic because the stage-info modal is wide enough to break is_modal_open's default probe point, and it does not close on Escape. See plan.md Phase 9.
The story sweep modal renders at least two different internal layouts:
- a plain one for the bonus
-Astage - a taller tabbed one for regular numbered stages
Their button coordinates differ. This was discovered live in Phase 10.
Live testing found both the mailbox-icon and cafe-icon fixed coordinates were flaky. They missed the first click and worked on retry. Neither task originally verified state before proceeding, so a missed click cascaded into blind actions and could reach an unverified Escape press on the home screen, which triggers Blue Archive's own "exit the game?" confirmation.
See plan.md Phases 5–6 for the full writeup. This is the concrete reason every task now verifies state before acting rather than trusting fixed coordinates or a single click blindly.
Rank-up popups mid-pat-loop are now handled.
A pat that crosses an affection-rank threshold shows a full-screen 絆ランクアップ! cutscene with no cafe header. find_cafe_sparkle() can never recognize this because it is nothing like the sparkle template. The loop used to spin uselessly against it for the rest of the room's click budget.
cafe.py's _dismiss_rank_up_if_shown() reuses the existing navigation.is_on_subscreen header-brightness probe. This was confirmed against screenshots/cafe/student/01-02:
- rank-up cutscene: header probe reads
r < 200 - normal cafe screen: header probe reads
r > 200
It presses Enter until the cutscene clears, porting the reference's own to_cafe() navigation handling of relationship_rank_up from module/cafe_reward.py.
This is not yet live-confirmed against a real cafe-pat rank-up trigger specifically. It is grounded in real captured screenshots, but treat it as implemented-but-unverified until one happens naturally during a real cafe run.
The same full-screen 絆ランクアップ! cutscene was confirmed live during Lesson/Schedule work (see plan.md Phase 12), and that work found is_on_subscreen's fixed header probe reads inconsistently across different characters' cutscene art — bright for one, which would make cafe's "keep pressing Enter while NOT on subscreen" loop think the cutscene had already cleared when it hadn't. Not necessarily hazardous on its own (it would just stop dismissing early and let the pat loop retry against a still-showing cutscene), but worth a more robust probe if cafe's rank-up path ever misbehaves live.
Not yet verified for cafe:
- whether camera zoom/pan can drift over a long unattended run
- the reference project zooms out before detecting
- this project currently does not
- testing did not reproduce a failure from skipping it
- see
plan.mdPhase 6 "Not verified" list
Known cafe gaps to verify during migration
When migrating or modifying cafe logic, verify these manually against the actual current behavior:
- whether student rotation popups are handled
- whether rank-up popups are handled
- whether the view is zoomed/centered before sparkle detection
- whether detection still works when text overlaps the student
- whether one Python process can handle repeated sparkle detection faster than repeated cold starts
- whether both cafe rooms are handled consistently
- whether cafe income claim is robust against popup timing
Do not assume the old Bash implementation handles these correctly.
Migration goal
The migration goal is:
- preserve existing behavior
- move feature logic from Bash into Python
- keep
ba_dailies.shas launcher only - make detection logic reusable
- prepare for future features by copying the reference project's structure where appropriate
Anti-patterns
Do not:
- add new
do_<feature>Bash functions - build a giant Bash automation script
- recreate reference logic as fixed-coordinate Bash click chains
- skip reading the reference module before implementing a feature
- put OCR in Bash
- put OpenCV state machines in Bash
- make every feature shell out independently to
xdotool - duplicate common navigation in every task
- copy reference image assets blindly
- edit
~/repo/baas-reference/ - implement event-specific features before the generic reusable machinery exists
- invent a pixel probe, fixed-coordinate, or randomized substitute for reference logic that uses OCR, just to avoid setting up OCR
- treat OCR as an Android-specific concern that can be deferred indefinitely
- run temporary probes from
/private/tmp/claude-*instead of the repositoryscratchpad/ - create investigation scripts outside the repository when they can live in
scratchpad/ - use multiline
python3 -c "..."commands for non-trivial probes - rely on Claude Code internal temporary paths in committed code, notes, commands, or debugging workflow
- write generated debug files into the repository root when they belong in
scratchpad/
Feature implementation workflow
For every new feature:
- Read the relevant
~/repo/baas-reference/module/...file. - Summarize the upstream feature flow in notes or comments.
- Add or update the reference mapping table.
- Identify missing local driver primitives.
- Implement or improve those primitives in
ba_auto/driver.pyorba_auto/detector.py. - Implement the feature in
ba_auto/tasks/<feature>.py. - Add CLI dispatch in
ba_daily.py. - Keep
ba_dailies.shunchanged unless launcher behavior changes. - Run syntax checks.
- Deploy to
nik-gpu. - Test against the live game.
- Update
plan.mdstatus. - Move temporary investigation files into
scratchpad/, or delete them if they are no longer useful.
Priority when uncertain
When uncertain, prefer this order:
- Preserve existing working behavior.
- Follow the reference project's control flow, including its use of OCR.
- Do not deprioritize OCR-based navigation/matching in favor of a simpler non-OCR substitute.
- Keep logic in Python.
- Add reusable driver primitives instead of feature-specific hacks.
- Use local screenshots/assets only when backend differences require it.
- Use
scratchpad/for temporary work and diagnostics. - Avoid large rewrites that do not move the project closer to reference-driven Python architecture.
User preference
The user wants this project to be as close to the original Blue Archive Auto Script architecture as practical, without recreating logic that already exists.
The user specifically does not want Claude Code to keep converting feature work into Bash.
The user also wants Claude Code to avoid using /private/tmp/claude-* for development probes when a repository-local scratchpad/ directory can be used instead, because those paths trigger permission prompts and interrupt the development flow.
Respect those preferences.