feat: implement handling for pending-update download notice during login

This commit is contained in:
Nik Afiq 2026-08-04 17:56:18 +09:00
parent a690e90478
commit ff240cb104
3 changed files with 125 additions and 2 deletions

View File

@ -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

View File

@ -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

12
plan.md
View File

@ -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