diff --git a/ba_auto/config.py b/ba_auto/config.py index 3a34499..35d47a7 100644 --- a/ba_auto/config.py +++ b/ba_auto/config.py @@ -1588,6 +1588,36 @@ LOGIN_LOADING_BUFFER_TOLERANCE = 12 # reason to wait the full 4 minutes once it's confidently identified. LOGIN_LOADING_BUFFER_STUCK_SECONDS = 60 +# Pending-update / additional-data download notice -- a real "お知らせ" +# (Notice) dialog that can appear immediately after launch, well before the +# title screen, reading "必要なコンポーネントをダウンロードします。" with a +# "容量"/MB size box and キャンセル/OK buttons. This is the same notice +# class as the reference's own main_page_game-download-resource-notice[23]/ +# main_page_download-additional-resources img_reactions in +# core/Baas_thread.py::to_main_page, which the reference answers with a +# fixed-coordinate click on a matched image template. This project has no +# template asset for it, so OCR on the body text is used instead (matching +# CLAUDE.md's OCR policy) -- see login.py's module docstring for the full +# live-incident writeup (a real q4h cron run got stuck here 2026-08-04 +# because this dialog previously fell through to the generic Enter +# fallback, which does NOT dismiss it, unlike every other one-off notice +# this flow has hit so far). +# +# Live-captured on nik-gpu at the native 1920x1200 +# (scratchpad/login_probe_01.png). LOGIN_UPDATE_DIALOG_TEXT_RECT crops the +# dialog's own body line; lang="jpn" OCR read it back as +# "必要なコンボーネントをダウンロードします。" (a minor ポ->ボ misread, +# harmless -- LOGIN_UPDATE_DIALOG_KEYWORD only checks for the substring +# "ダウンロード", which read back correctly). LOGIN_UPDATE_OK_BUTTON is a +# point on the OK button's own bright-cyan fill (confirmed via a pixel scan +# across the button's full width, (107,216,253)-(122,222,255) throughout +# except two x-columns landing on the dark-navy "OK" glyph strokes +# themselves) -- deliberately not the button's visual center, to stay clear +# of the text. +LOGIN_UPDATE_DIALOG_TEXT_RECT = (600, 420, 1320, 465) +LOGIN_UPDATE_DIALOG_KEYWORD = "ダウンロード" +LOGIN_UPDATE_OK_BUTTON = (1040, 851) + # Battle Pass (バトルパス) -- ported from baas-reference's # module/collect_pass_reward.py. See ba_auto/tasks/battle_pass.py's module # docstring for the full reference-mapping writeup; constants below were diff --git a/ba_auto/tasks/login.py b/ba_auto/tasks/login.py index 81fbe2c..2a139c5 100644 --- a/ba_auto/tasks/login.py +++ b/ba_auto/tasks/login.py @@ -244,10 +244,66 @@ live-confirmed against a fresh real stuck instance recovering via this faster path specifically (the instance that prompted this fix had already recovered via the pre-existing generic-timeout path by the time the fix was written). + +**Pending-update download notice, `_update_dialog_open` (2026-08-04)**: the +user reported "the game contains a pending update... login doesn't adapt to +the popup," and a real q4h cron run that same day (17:00 JST) confirmed it +first-hand -- `~/ba_logs/q4h.log` showed `[login] not home after the +timeout -- killing and relaunching the game` followed by `could not reach +home after 2 attempts`, then every subsequent task (cafe, event_sweep, ...) +failed to open its own screen, the same cascading-failure shape as the +2026-07-17 session-death incident above but with a different root cause. +By the time this was investigated the game had been fully killed (exhausted +relaunch budget), so it was relaunched deliberately to reproduce the state +live rather than guessed at. A real "お知らせ" (Notice) dialog appeared +immediately after launch, well before the title screen, reading +"必要なコンポーネントをダウンロードします。" with a "容量"/MB size box and +キャンセル/OK buttons (`scratchpad/login_probe_01.png`) -- this is the same +notice class as the reference's own `main_page_game-download-resource- +notice[23]`/`main_page_download-additional-resources` img_reactions in +`core/Baas_thread.py::to_main_page`, which the reference answers with a +fixed-coordinate click on a matched image template; this project has no +template asset for it, so OCR on the body text is used instead (matching +CLAUDE.md's OCR policy of porting the reference's actual state-detection +approach rather than substituting a pixel-probe/coordinate guess). This +dialog previously fell through to the generic Enter-fallback branch like +every other unrecognized state -- confirmed live that Enter does NOT +dismiss it, unlike every other one-off notice this flow has hit so far, +which is exactly why the stuck cron run never resolved within +`LOGIN_TIMEOUT_SECONDS` and had to be killed and relaunched, only to hit +the same still-pending update again on relaunch. Fixed with +`_update_dialog_open`, an OCR check (`config.LOGIN_UPDATE_DIALOG_TEXT_RECT`, +lang="jpn") for the "ダウンロード" keyword in the dialog body, gating a +real click on `config.LOGIN_UPDATE_OK_BUTTON` -- inserted into +`_wait_for_home`'s existing dispatch chain gated on `_logo_state` reading +"absent" (rather than as its own check on every iteration), since it can +only matter on the exact branch that was getting stuck; "bright"/"dimmed" +are already known to be the plain title screen. Clicking OK triggers a real +~230MB download with its own progress readout, then a "ゲームデータを +イニシャライズしています……" (initializing) screen +(`scratchpad/login_probe_02.png`/`_03.png`), landing back on the ordinary +title screen afterward (`_04.png`) -- no new handling needed for either +intermediate screen, since neither has an interactive element and both +already fall through harmlessly to the same Enter fallback (a no-op on a +non-interactive splash screen) until the title screen's own bright-logo +check takes over. **Confirmed live end-to-end twice the same day**: first +via direct `xdotool`/`scrot` calls to calibrate the OCR rect/keyword and +button coordinate against the real dialog before writing any code, then +again for real via the actual deployed code path -- after this fix was +rsynced to nik-gpu, a real `./ba_dailies.sh login` run hit an unrelated +stuck-loading-buffer timeout, killed and relaunched the game, and the +still-pending update notice reappeared on that fresh relaunch. The log +shows it plainly: `[login] update/download notice detected -- clicking OK` +immediately followed by `[login] reached home`, and a follow-up screenshot +confirmed a genuine, fully-interactive home screen (full HUD: AP, credits, +gems, nav bar). Both the detection (`_update_dialog_open`) and the click +(`LOGIN_UPDATE_OK_BUTTON`) fired correctly on their first real, +un-rehearsed encounter through the normal dispatch path -- not just the +manual walkthrough used to calibrate them. """ import time -from ba_auto import navigation +from ba_auto import detector, navigation def _color_matches(rgb, target, tolerance): @@ -281,6 +337,18 @@ def _loading_buffer_visible(driver, config): return all(_color_matches(c, config.LOGIN_LOADING_BUFFER_RGB, config.LOGIN_LOADING_BUFFER_TOLERANCE) for c in colors) +def _update_dialog_open(driver, config): + """Positive OCR check for the pending-update/additional-data download + notice (see config.py's LOGIN_UPDATE_DIALOG_* comment for the full + live-incident writeup and calibration). Only called when _logo_state + reads "absent" -- "bright"/"dimmed" are already known to be the plain + title screen, not this dialog, so there's no reason to pay for an OCR + call on every single poll. + """ + text = detector.read_text(config.LOGIN_UPDATE_DIALOG_TEXT_RECT, psm=6, lang="jpn") + return config.LOGIN_UPDATE_DIALOG_KEYWORD in text + + def _true_home(driver, config): return ( navigation.is_home_nav_bar_visible(driver) @@ -374,10 +442,23 @@ def _wait_for_home(driver, config): return True print("[login] home check didn't hold on re-verification -- treating as a coincidental frame, retrying") continue + logo_state = _logo_state(driver, config) if _news_dialog_open(driver, config): driver.click(*config.LOGIN_NEWS_CLOSE_BUTTON) - elif _logo_state(driver, config) == "bright": + elif logo_state == "bright": driver.click(*config.LOGIN_TOUCH_TO_START) + elif logo_state == "absent" and _update_dialog_open(driver, config): + # Pending-update/additional-data download notice -- does NOT + # respond to Enter (confirmed live 2026-08-04, see module + # docstring), unlike every other dialog in this else-branch, so + # it needs its own real button click rather than falling + # through to the generic fallback below. Gated on logo_state + # == "absent" (rather than checked unconditionally) so the OCR + # call is only paid for on the one branch it can actually + # matter on -- "bright"/"dimmed" are already known to be the + # plain title screen, not this dialog. + print("[login] update/download notice detected -- clicking OK") + driver.click(*config.LOGIN_UPDATE_OK_BUTTON) else: # Dimmed title-screen notice, a loading transition, the daily # attendance card, an infrequent login-bonus card, or any other diff --git a/plan.md b/plan.md index f70eba3..13d5e00 100644 --- a/plan.md +++ b/plan.md @@ -1237,6 +1237,18 @@ Implementation: `ba_auto/config.py` (new `SCRIMMAGE_*` section, kept fully decou **Not yet exercised**: a day where the date-rotation picks ゲヘナ or ミレニアム specifically (only トリニティ was live-tested, since that's what today's rotation picked), an `inadequate_ticket` outcome (the account had 30 tickets, comfortably more than one MAX sweep needed), and a day where the AP guard actually fires and cancels (the monthly pass was active throughout this session, so `ap_cost != 0` was never really hit) -- all implemented per the same defensive shape confirmed working elsewhere in this project (bounty.py's own `_is_ticket_purchase_prompt`/fail-closed conventions), just not yet observed against real divergent game state. +### Phase 28: Login pending-update download notice (2026-08-04) + +User report: "Currently the game contain pending update. Login feature currently doesn't adapt to the popup in the login screen to install update." A real `q4h` cron run that same day (17:00 JST) confirmed it independently before any code was touched: `~/ba_logs/q4h.log` showed `[login] not home after the timeout -- killing and relaunching the game` then `could not reach home after 2 attempts`, cascading into cafe/event_sweep/mailbox/stamina all failing to open their own screens for the rest of that run -- the same cascading-failure shape as the 2026-07-17 session-death incident (Phase 18 follow-up #3) but a different root cause. + +By the time this was investigated the game had been fully killed (relaunch budget exhausted). Rather than guess at the popup's shape, the game was deliberately relaunched live on nik-gpu to reproduce it. A real "お知らせ" (Notice) dialog appeared immediately after launch, well before the title screen: "必要なコンポーネントをダウンロードします。" with a "容量"/MB size box and キャンセル/OK buttons (`scratchpad/login_probe_01.png`). Per the reference-first rule, checked `core/Baas_thread.py::to_main_page` first -- this is the exact same notice class as its own `main_page_game-download-resource-notice[23]`/`main_page_download-additional-resources` img_reactions, which the reference answers with a fixed-coordinate click on a matched image template. This project has no template asset for it, so OCR on the dialog's own body text is used instead (`config.LOGIN_UPDATE_DIALOG_TEXT_RECT`, lang="jpn", matching on the substring "ダウンロード"), gating a real click on `config.LOGIN_UPDATE_OK_BUTTON` -- both calibrated directly against the live dialog (OCR read back "必要なコンボーネントをダウンロードします。", a harmless ポ->ボ misread; the button coordinate was chosen from a pixel scan across the OK button's full width to land clear of the "OK" glyph itself). + +Confirmed live that Enter -- this flow's established generic fallback for every other one-off notice (network-error, daily attendance card, etc.) -- does NOT dismiss this dialog, which is exactly why the stuck cron run never resolved within `LOGIN_TIMEOUT_SECONDS` and had to be killed and relaunched, only to hit the same still-pending update again. Fixed with `_update_dialog_open` in `ba_auto/tasks/login.py`, inserted into `_wait_for_home`'s existing dispatch chain gated on `_logo_state` reading `"absent"` (not checked unconditionally every poll) -- clicking OK triggers a real ~230MB download with its own progress readout, then a "ゲームデータをイニシャライズしています……" screen, landing back on the ordinary title screen afterward (`scratchpad/login_probe_02.png`/`_03.png`/`_04.png`); neither intermediate screen needed new handling since both already fall through harmlessly to the existing Enter fallback (a no-op on a non-interactive splash) until the title screen's own bright-logo check takes over. + +`py_compile` passed locally; deployed via the standard `rsync` push. + +**Confirmed live end-to-end twice the same day.** First via direct `xdotool`/`scrot` calibration (not through the actual code path). Then, after deploying, a real `./ba_dailies.sh login` run hit an unrelated stuck-loading-buffer timeout, killed and relaunched the game itself, and the still-pending update notice reappeared on that fresh relaunch -- the log shows `[login] update/download notice detected -- clicking OK` immediately followed by `[login] reached home`, and a follow-up screenshot confirmed a genuine, fully-interactive home screen (full HUD: AP 60/240, credits, gems, nav bar). Both the OCR detection and the button click fired correctly on their first real, un-rehearsed encounter through the normal dispatch path, not just the manual calibration walkthrough. + ## Prerequisites ### OCR