Skip to content

Conversation

@JoshCai233
Copy link
Contributor

@JoshCai233 JoshCai233 commented Dec 7, 2025

Summary by CodeRabbit

发布说明

  • 重构
    • 大量界面识别流程改为使用缓存的 last_screenshot 代替实时截图,统一截图来源以减少重复捕获并可能改善性能与稳定性。
  • Chore
    • 若干操作节点调整了预回合截图行为(新增 screenshot_before_round=False),以改变截图时机,优化流程执行时序。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 7, 2025

Walkthrough

本次 PR 大量将各处获取屏幕的调用由即时截屏 self.screenshot() 改为使用缓存的 self.last_screenshot,并在若干 @operation_node 装饰器上追加 screenshot_before_round=False,未修改方法签名或对外接口。

Changes

Cohort / File(s) 变更摘要
核心截图替换(广泛)
src/sr_od/app/..., src/sr_od/operations/..., src/sr_od/sr_map/..., src/sr_od/challenge_mission/..., src/sr_od/interastral_peace_guide/...
将大量位置的 self.screenshot() 替换为 self.last_screenshot,使各方法使用缓存的最近截图作为识别/点击输入;未改动方法签名或主要控制流。
SimUni / 移动与战斗子系统
src/sr_od/app/sim_uni/..., src/sr_od/app/sim_uni/operations/move_v1/*.py, src/sr_od/app/sim_uni/operations/move_v2/*.py, src/sr_od/app/sim_uni/operations/battle/*.py
多处将即时截屏切换为 last_screenshot;若干 operation_node 增加 screenshot_before_round=False(影响预截屏行为)。
装饰器调整
src/sr_od/operations/enter_game/switch_account.py, src/sr_od/operations/interact/catapult.py, src/sr_od/operations/interact/move_interact.py, 及若干 move_v2 路由文件
在若干 @operation_node 上添加 screenshot_before_round=False,改变节点预截屏配置。
零散改动(注释 / 新分支)
src/sr_od/operations/move/move_directly.py, src/sr_od/app/relic_salvage/relic_salvage_app.py
添加一条注释;在 choose_abandon 中增加了一个 else 分支返回 round_success('无需选择')
其它(商店、合成、队伍、邮件、引导等)
src/sr_od/operations/store/*.py, src/sr_od/operations/synthesize/*.py, src/sr_od/operations/team/*.py, src/sr_od/app/claim_email/email_app.py, src/sr_od/interastral_peace_guide/*
将各处截图调用切换为 last_screenshot,局部类型注释删除,控制流保持不变。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

需要额外关注的点:

  • 依赖“实时”帧的逻辑(移动、战斗、交互节点)是否仍能在使用缓存截图时保持正确性,例如 move_without_pos.py、各移动路由与战斗检测文件。
  • 装饰器新增的 screenshot_before_round=False 对节点执行顺序与截图时机的影响(move_interact.pycatapult.py、若干 move_v2 路由)。
  • last_screenshot 为空或未及时更新时的容错与重试路径(如登录、切换队伍、购买流程)。

Possibly related PRs

  • Dev 20251019 #531 — 在 sim_uni_fight_elite 的 _check_enemy_fight 中修改了相同函数,和本次将截图源切换为 last_screenshot 的改动存在代码级重叠。

Poem

我是小兔子,抱着一张旧屏 🐇
last_screenshot 温柔又坚定,
点击识别走熟路,逻辑仍旧稳,
少些再拍的等待,多些代码心安,
啾啾跳跃为改动轻声庆。

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题清晰准确地概括了主要变更:将self.screenshot()替换为self.last_screenshot以删除冗余截图,标题简洁且具体反映了核心改动。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_bless.py (2)

70-79: first_wait 使用缓存截图是合理的,但 skip_first_screen_check 分支逻辑存在问题

  • 本次把 screen = self.screenshot() 改成 screen = self.last_screenshot 本身是合理的,能避免重复截图,只要框架保证每轮节点调用前都会刷新 last_screenshot 即可。

  • 但当前判断条件:

    if not self.first_screen_check or not self.skip_first_screen_check:
        self.first_screen_check = False
        if not sim_uni_screen_state.in_sim_uni_choose_bless(screen, self.ctx.ocr):
            return self.round_retry('未在模拟宇宙-选择祝福页面', wait=1)

    与参数注释“skip_first_screen_check: 是否跳过第一次的画面状态检查”不符:

    • 默认 self.first_screen_check=Trueskip_first_screen_check=True 时,条件恒为 False,意味着永远不会做画面校验self.first_screen_check 也一直不会被置为 False。
    • skip_first_screen_check=False 时则每次都会进入分支,等价于“从不跳过首帧”,也和名称不符。

建议在本 PR 或后续 PR 中修正为“仅首帧可跳过,其余帧都检查”,类似下面的实现(仅示意):

 def first_wait(self):
-        screen = self.last_screenshot
-
-        if not self.first_screen_check or not self.skip_first_screen_check:
-            self.first_screen_check = False
-            if not sim_uni_screen_state.in_sim_uni_choose_bless(screen, self.ctx.ocr):
-                return self.round_retry('未在模拟宇宙-选择祝福页面', wait=1)
-
-        return self.round_success(wait=1)  # 稍微等待祝福都出现了
+        screen = self.last_screenshot
+
+        # 第一次且允许跳过首帧检查:只更新标志并直接放行
+        if self.first_screen_check and self.skip_first_screen_check:
+            self.first_screen_check = False
+            return self.round_success(wait=1)  # 稍微等待祝福都出现了
+
+        # 之后每次都进行页面校验
+        self.first_screen_check = False
+        if not sim_uni_screen_state.in_sim_uni_choose_bless(screen, self.ctx.ocr):
+            return self.round_retry('未在模拟宇宙-选择祝福页面', wait=1)
+
+        return self.round_success(wait=1)  # 稍微等待祝福都出现了

这样既保留“首帧可跳过”的优化,又不会导致整个节点完全不做状态校验。


83-113: choose_bless_time 仅在兜底分支赋值,fast_back_to_world=True 时可能导致运行时异常

这段逻辑中:

self.ctx.controller.click(target_bless_pos.rect.center)
time.sleep(0.25)

result = self.round_by_ocr_and_click(
    screen=screen,
    target_cn='确认',
)
if result.is_success:
    return self.round_success(status=result.status, wait=0.1)
# ...
self.ctx.controller.click(confirm_point)
self.choose_bless_time = time.time()
return self.round_success(wait=0.1)
  • handle_init 中将 self.choose_bless_time 初始化为 None

  • 这里只有在 OCR 未识别到确认按钮,走兜底点击固定坐标 时才给 self.choose_bless_time 赋值。

  • 但在 wait_not_in_blessfast_back_to_world 分支中会无条件做:

    if sim_uni_screen_state.in_sim_uni_choose_bless(self.ctx, screen):
        if now - self.choose_bless_time >= 2:
            ...

当:

  1. fast_back_to_world=True
  2. round_by_ocr_and_click 正常识别到“确认”并返回成功,

时,self.choose_bless_time 仍为 Nonenow - self.choose_bless_time 会抛出 TypeError(float 与 NoneType 相减)。

建议在点击祝福后就统一记录时间,而不是只在兜底分支中设置,例如:

         else:
             self.ctx.controller.click(target_bless_pos.rect.center)
-            time.sleep(0.25)
+            self.choose_bless_time = time.time()
+            time.sleep(0.25)
@@
-            self.ctx.controller.click(confirm_point)
-            self.choose_bless_time = time.time()
-            return self.round_success(wait=0.1)
+            self.ctx.controller.click(confirm_point)
+            return self.round_success(wait=0.1)

这样无论是 OCR 成功路径还是兜底路径,wait_not_in_bless 中都能拿到有效的 choose_bless_time,避免运行时异常。
本 PR 将 screen 改为使用 self.last_screenshot 不会影响上述修正建议。

🧹 Nitpick comments (8)
src/sr_od/operations/team/check_team_members_in_world.py (1)

28-35: 改用 last_screenshot 的前置条件和兜底可以再确认一下

这里直接用 self.last_screenshot 作为整轮识别的输入,从调用方式上看与原来的 self.screenshot() 兼容,只要调度层在进入本节点前已经刷新过最新截图,就能达到减少冗余截图的目的。

可以考虑两点小优化以提高鲁棒性:

  • 明确(或在注释中标注)本节点依赖的前置条件:进入本节点前框架一定会刷新 last_screenshot
  • 或者在这里做一次轻量兜底,例如当 last_screenshot 为空/无效时回退到 self.screenshot() 再继续识别,避免在某些边缘流程下用到过期或空截图导致后续 OCR / 特征匹配异常。
src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (1)

58-60: 确认节点同样完全依赖装饰器刷新截图,建议轻量防御

_confirm_path 现在也直接使用 self.last_screenshot,逻辑上与上一个节点保持一致,可以减少一次截图。但同样完全依赖 @operation_node 在每轮开始前更新 last_screenshot

两个小建议供参考:

  • 若以后给该节点加上 screenshot_before_round=False(例如为了优化性能),需要记得同步在节点内部恢复主动截图,否则这里会用到旧图。
  • 可以在需要强依赖截图的节点附近加一行注释,或在 round_by_find_and_click_area 内对 screen is None 做个早期保护(直接 round_retry),增强健壮性。

当前改动本身是合理的,只是建议你在整体 PR 里再扫一遍这些依赖关系。

src/sr_od/operations/team/switch_member.py (1)

79-83: 确认对 last_screenshot 的依赖是否始终安全

这里从主动 self.screenshot() 改为直接使用 self.last_screenshot,逻辑上依赖于:

  1. @operation_node 在每轮调用 _wait_after_confirm 之前都会自动更新一次 last_screenshot
  2. 对这个节点当前和将来都不会设置 screenshot_before_round=False,也不会改变默认行为。

在当前框架契约成立的前提下,这样可以有效避免重复截图、性能更好;但如果以后有人调整该节点的 operation_node 参数或装饰器默认值,这里就可能拿到旧画面或 None,导致误判或异常。

建议你:

  • 明确确认一下当前框架对该节点的截图时机保证;
  • 如果希望提高健壮性,可以考虑在装饰器上显式标注一次 screenshot_before_round=True,以防默认值将来被改掉(保持零额外截图开销的前提下锁定契约)。
src/sr_od/challenge_mission/choose_support_in_team.py (1)

40-150: 建议进行端到端测试验证功能正确性

本次改动将所有节点的截图方式从 self.screenshot() 改为 self.last_screenshot,这是一个减少冗余截图的性能优化。建议:

  1. 功能测试:在实际环境中运行完整的支援角色选择流程,特别关注需要滚动列表查找角色的场景(触发第 81 行的拖动操作)
  2. 性能对比:对比改动前后的截图次数和执行时间,验证优化效果
  3. 边缘情况:测试找不到支援角色的情况(触发第 72-76 行的逻辑)

如果测试中发现识别失败,可能需要在某些关键位置(如拖动操作后)重新添加主动截图。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_move_to_next_level_v3.py (1)

205-245: 需要重点测试该高频检测方法

该方法是实时控制的核心,包含高频率的屏幕状态检查(等待时间仅 0.02 秒)。使用 self.last_screenshot 可能存在以下风险:

  1. 交互时机判断(第 229 行):移动过程中检测交互机会,若截图数据过时可能导致错过交互或误判
  2. 大世界状态检测(第 207 行):交互后需要快速判断是否已离开大世界,延迟可能导致逻辑错误
  3. 入口匹配(第 234 行):实时匹配下层入口位置,过时数据可能导致转向不准确

建议对该方法进行充分的实际场景测试,验证在高频调用下 last_screenshot 的新鲜度是否满足检测精度要求。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py (1)

101-131: _detect_screen 禁用前置截图逻辑正确,注释可顺带更新

这里在装饰器上设置 screenshot_before_round=False,由函数内部视角下拉后再调用 self.screenshot() 做识别,避免多余截屏且保证检测用图像是最新画面,和路由逻辑是匹配的。顺带一提,docstring 仍写着“识别游戏画面上是否有事件牌”,当前逻辑识别的是“黑塔”,后续有空可以把注释改成更贴切的描述。

src/sr_od/app/assignments/assignments_app.py (1)

54-58: 委托流程统一使用 last_screenshot,建议后续顺带精简 _click_assignment 的截图

choose_tab_claim_allassign_again_claim_click_empty 都改为从 self.last_screenshotscreen 传给 OCR/查找辅助方法;这些节点在读取前没有对画面做新的点击或移动,且装饰器使用默认 screenshot_before_round=True,所以每轮进入节点前的那张截图就足够用,领取/再次派遣流程行为不变但少了多次 self.screenshot()。可以考虑后续把 _click_assignment 里单独的 self.screenshot() 也改为复用 last_screenshot(或者在该节点上设置 screenshot_before_round=False,只保留内部截图),进一步统一和精简截屏逻辑。

Also applies to: 62-69, 73-76, 80-88, 92-95

src/sr_od/operations/enter_game/enter_game.py (1)

153-156: logout_with_account_kept 中对旧 screen 的复用可以更稳健

这里先基于 last_screenshot 构造 screen,然后调用 round_by_click_area 点击“退出并保留登陆记录”,接着仍把点击前的 screen 传给 round_by_find_and_click_area 查找“退出”按钮。根据 round_by_find_and_click_area 的实现(始终使用传入的 screen),如果“退出”按钮是点击后才出现的,这一轮看到的始终是旧画面。这个行为在本次改动前就存在,只是现在顺手可以考虑把第二个调用的 screen 参数改成 None,让它直接使用当前最新的 last_screenshot 做查找,会更贴近预期。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b55bee and a2830ab.

📒 Files selected for processing (60)
  • src/sr_od/app/assignments/assignments_app.py (3 hunks)
  • src/sr_od/app/daily_training/daily_training_app.py (1 hunks)
  • src/sr_od/app/div_uni/operations/choose_oe_file.py (3 hunks)
  • src/sr_od/app/div_uni/operations/choose_oe_support.py (3 hunks)
  • src/sr_od/app/div_uni/operations/ornamenet_extraction.py (4 hunks)
  • src/sr_od/app/nameless_honor/nameless_honor_app.py (1 hunks)
  • src/sr_od/app/relic_salvage/relic_salvage_app.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/reset_sim_uni_level.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_level.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_world.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_wait_level_start.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/battle/sim_uni_fight_elite.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_bless.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_drop_bless.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_upgrade_bless.py (5 hunks)
  • src/sr_od/app/sim_uni/operations/curio/sim_uni_choose_curio.py (4 hunks)
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/move_to_next_level.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_detect.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_mm.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_interact_by_detect.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_move_to_next_level_v3.py (7 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_elite_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_enter_fight.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_event.py (4 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_exit.py (4 hunks)
  • src/sr_od/app/sim_uni/sim_uni_app.py (1 hunks)
  • src/sr_od/app/world_patrol/world_patrol_enter_fight.py (1 hunks)
  • src/sr_od/challenge_mission/choose_support_in_team.py (5 hunks)
  • src/sr_od/interastral_peace_guide/guide_check_power.py (1 hunks)
  • src/sr_od/interastral_peace_guide/guide_choose_category.py (2 hunks)
  • src/sr_od/interastral_peace_guide/guide_transport.py (1 hunks)
  • src/sr_od/interastral_peace_guide/open_guide.py (2 hunks)
  • src/sr_od/operations/back_to_normal_world_plus.py (1 hunks)
  • src/sr_od/operations/battle/start_fight_for_elite.py (1 hunks)
  • src/sr_od/operations/cancel_mission_trace.py (1 hunks)
  • src/sr_od/operations/click_dialog_confirm.py (1 hunks)
  • src/sr_od/operations/enter_game/enter_game.py (3 hunks)
  • src/sr_od/operations/enter_game/switch_account.py (1 hunks)
  • src/sr_od/operations/interact/catapult.py (1 hunks)
  • src/sr_od/operations/interact/move_interact.py (1 hunks)
  • src/sr_od/operations/move/move_directly.py (1 hunks)
  • src/sr_od/operations/move/move_without_pos.py (1 hunks)
  • src/sr_od/operations/store/buy_store_item.py (4 hunks)
  • src/sr_od/operations/synthesize/synthesize.py (7 hunks)
  • src/sr_od/operations/team/check_team_members_in_world.py (1 hunks)
  • src/sr_od/operations/team/choose_support.py (3 hunks)
  • src/sr_od/operations/team/switch_member.py (1 hunks)
  • src/sr_od/operations/technique.py (8 hunks)
  • src/sr_od/operations/wait/wait_in_world.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_floor.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_planet.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_region.py (3 hunks)
  • src/sr_od/sr_map/operations/choose_special_point.py (1 hunks)
  • src/sr_od/sr_map/operations/open_map.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-13T12:07:30.874Z
Learnt from: JoshCai233
Repo: OneDragon-Anything/StarRailOneDragon PR: 521
File: src/sr_od/app/sim_uni/sim_uni_app.py:61-87
Timestamp: 2025-10-13T12:07:30.874Z
Learning: 在 `_check_points_reward()` 中,`OperationRoundResult` 的语义与常规不同:`FAIL` 表示"奖励未完成,需要继续执行自动化",而 `SUCCESS` 和 `RETRY` 表示"提前返回,跳过自动化"。这是因为调用方使用 `if result != FAIL: return` 来判断是否跳过后续的自动化脚本执行。

Applied to files:

  • src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py
  • src/sr_od/app/daily_training/daily_training_app.py
  • src/sr_od/operations/enter_game/switch_account.py
  • src/sr_od/operations/wait/wait_in_world.py
  • src/sr_od/interastral_peace_guide/open_guide.py
  • src/sr_od/interastral_peace_guide/guide_check_power.py
  • src/sr_od/app/relic_salvage/relic_salvage_app.py
  • src/sr_od/operations/interact/catapult.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py
  • src/sr_od/app/assignments/assignments_app.py
  • src/sr_od/operations/back_to_normal_world_plus.py
  • src/sr_od/operations/click_dialog_confirm.py
  • src/sr_od/sr_map/operations/choose_special_point.py
  • src/sr_od/operations/enter_game/enter_game.py
  • src/sr_od/operations/technique.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_elite_route_v2.py
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py
  • src/sr_od/operations/interact/move_interact.py
🧬 Code graph analysis (7)
src/sr_od/operations/enter_game/switch_account.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/operations/interact/catapult.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/assignments/assignments_app.py (1)
src/one_dragon/base/operation/operation.py (1)
  • round_by_find_and_click_area (789-857)
src/sr_od/operations/interact/move_interact.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/div_uni/operations/choose_oe_file.py (3)
src/one_dragon/base/operation/operation.py (2)
  • round_by_find_area (859-883)
  • status (51-55)
src/one_dragon/base/operation/operation_edge.py (1)
  • node_from (64-95)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
🔇 Additional comments (93)
src/sr_od/operations/enter_game/switch_account.py (1)

26-43: 改动合理,逻辑清晰。

添加 screenshot_before_round=False 可以避免在 round 开始时自动截图,而方法内部在 time.sleep(2) 之后(第41行)手动调用 self.screenshot() 获取截图用于后续的确认按钮识别。这样的设计更合理:

  1. 第一次点击(第34行)可能不需要最新截图
  2. 等待 UI 响应后再截图,确保截图内容是最新的

这与 PR 目标"删除冗余截图"一致。

src/sr_od/operations/interact/catapult.py (1)

57-71: 关闭轮前截图以避免重复截图是合理的

这里设置 screenshot_before_round=False,而方法内部仍在 time.sleep(3) 之后主动调用 self.screenshot(),因此不会出现“没图可识别”的问题,只是去掉了框架层多余的一次自动截图,符合本 PR 减少冗余截图的目标,逻辑上是安全的。

src/sr_od/operations/interact/move_interact.py (1)

51-80: 手动截图已覆盖需求,关闭轮前自动截图是安全的优化

check_screen 在每次执行时都会先短暂等待再调用 self.screenshot(),因此将装饰器改为 screenshot_before_round=False 仅移除了框架层的一次自动截图,不会影响交互按钮识别与后续逻辑,能减少一次冗余截图,改动是合理的。

src/sr_od/operations/click_dialog_confirm.py (1)

24-28: Confirm call paths for last_screenshot change

This change from self.screenshot() to self.last_screenshot aligns with the PR's goal of reducing redundant screenshots. However, the behavioral safety depends on how ClickDialogConfirm.click() is invoked:

  • If called exclusively through the @operation_node framework (which auto-updates screenshots before each round), the change is safe and reduces overhead.
  • If called directly elsewhere without prior screenshot capture, the method will use a potentially stale screenshot.

Verify that ClickDialogConfirm.click() has no direct callers outside the operation framework, or confirm that all call sites ensure last_screenshot is current.

src/sr_od/app/sim_uni/operations/bless/sim_uni_drop_bless.py (2)

56-69: 改用 last_screenshot 符合“去重复截图”的思路

这里直接复用 self.last_screenshot,与框架统一使用上一帧截图的策略一致,传入给 get_sim_uni_screen_state 的仍然是本轮画面,逻辑上没有引入额外分支或副作用,看起来是安全的重构。


72-85: 选择祝福节点同样复用上一帧截图,注意确认刷新时机

choose_bless 也改为使用 self.last_screenshot,能减少一次主动截图调用,只要 SrOperation/装饰器在进入该节点前保证 last_screenshot 已刷新,这里识别祝福的位置与原先 self.screenshot() 的行为应保持一致。建议在实际运行一两轮模拟宇宙丢弃祝福流程,确认识别成功率无回退。

src/sr_od/operations/move/move_directly.py (1)

111-112: Verify operation_node decorator's default screenshot_before_round behavior.

The TODO comment correctly questions whether the explicit self.screenshot() call is necessary. Other files in this PR are adding screenshot_before_round=False to the decorator, suggesting the parameter defaults to True and automatically captures a screenshot before each round. If this is the case, self.last_screenshot would already contain the current frame, making the explicit call redundant and replaceable with self.last_screenshot.

Check the operation_node decorator definition to confirm the default value of screenshot_before_round and decide whether to remove or keep this call.

src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (1)

39-53: 使用 last_screenshot 的前置条件需要确认

这里改为直接复用 self.last_screenshot,可以去掉多余的 self.screenshot(),思路没问题。但这依赖于:

  • @operation_node 在每一轮开始前一定会刷新截图并写入 last_screenshot
  • 尤其是这是 is_start_node=True 的起始节点,首轮调用时也必须保证 last_screenshot 已被正确设置。

建议你确认一下当前装饰器的默认参数(是否 screenshot_before_round=True),以及重试路径下 round_retry 回来是否总是会在下一轮前重新截图,以避免拿到 None 或过期截图的情况。

src/sr_od/operations/synthesize/synthesize.py (7)

58-60: 使用 last_screenshot 的前置假设需要确认

这里改为直接使用 self.last_screenshot,在减少重复截图上是合理的;前提是 SrOperation / @operation_node 在进入该起始节点前,一定已经更新过 last_screenshot(且不是上一个操作残留的画面)。否则首次进入或异常路径下可能拿到 None 或过期截图,导致识别异常但不一定显性报错。

建议确认框架中该节点的 screenshot_before_round 默认值及调用顺序;若存在任何不确定场景,可以在此节点保留 self.screenshot() 作为兜底。


74-78: 与起始节点同样的截图策略变更

这里同样改为使用 self.last_screenshot 进行分类标题 OCR,逻辑本身未变,只依赖框架在本轮调用前已更新截图。行为与 check_screen 中的改动一致,请一并参考对行 58 的说明。


92-94: 选择物品节点复用上一轮截图,看起来是等价替换

choose_item 中仅在本轮开始时做一次截图读取用于模板匹配,失败时通过拖动并 round_retry 进入下一轮再重新识别。改为 self.last_screenshot 后,只要每轮前由装饰器刷新截图,行为与原先调用 self.screenshot() 等价。


132-139: 数量选择节点使用缓存截图进行“材料不足”检测

这里用 last_screenshot 先做一次“材料不足”区域识别,之后点击“最大值”按钮本身不依赖后续截图更新。改动在逻辑上是安全的,同样依赖每轮前的截图刷新机制;和之前几个节点保持了一致的风格。


148-150: 点击“合成”按钮的识别逻辑复用上一帧,需确保对话框出现时重新截图

click_synthesize 使用 round_by_find_and_click_area 配合 screen = self.last_screenshot,只要在本轮开始时获取的就是当前界面(包含“合成”按钮),则与原先行为等价。这里没有在节点内产生新的状态变化再做二次识别,因此复用一帧截图是合理的。


159-161: 确认对话框识别依赖最新一轮截图

click_confirm 需要在“点击合成”之后的新界面上寻找“合成确认”按钮。若调度器在进入本节点前会重新截图并刷新 last_screenshot,此处改动就是单纯去除冗余调用,没有逻辑风险;否则会出现仍在使用点击前画面的情况。建议在整体流程跑一遍实测该对话框是否能稳定被识别。


170-172: 关闭空白点击节点的截图复用与前面保持一致

click_empty 的逻辑与前一节点类似,仅在本轮开始时读取一次截图查找“点击空白处关闭”的区域。只要进入该节点前有重新截图,复用 last_screenshot 不影响功能,并统一了本文件的写法。

src/sr_od/app/div_uni/operations/ornamenet_extraction.py (3)

132-135: 点击挑战节点改用 last_screenshot

这里将 self.screenshot()(推测)改为使用 self.last_screenshot,只变更截图来源、不改业务逻辑,看起来与“减少重复截图”的 PR 目标一致,没有明显风险。


143-145: 等待类节点使用 last_screenshot 时需确认刷新时机

wait_mission_loadedwait_back 都是在“等待大世界角色图标”的节点中使用 self.last_screenshot 传给 round_by_find_area。从代码上看逻辑是合理的,但有一点需要确认:

  • 如果节点在 node_max_retry_times 内会多次重试,而 round_by_find_area 只依赖传入的 screen,则需要确保在每一轮重试前框架都会刷新 last_screenshot,否则可能一直用旧图导致识别失败或变慢。

建议你确认一下 Operation 框架里 last_screenshot 的更新策略(例如在每轮/每节点前自动截图并更新),确保这两个等待节点在重试时拿到的是最新画面;如果已经有保证则这处改动是安全的。

Also applies to: 205-206


189-196: 战斗结果处理节点复用上一帧截图

after_battle_result 中复用 self.last_screenshot 来判断点击“再来一次按钮”或“退出关卡按钮”,与上游 WaitBattleResult 刚结束时的 UI 状态是一致的,避免了额外截图,逻辑上也保持不变,这里改动是合理的。

src/sr_od/sr_map/operations/choose_special_point.py (1)

35-79: 确认该节点的 last_screenshot 每轮都会被正确刷新

这里从 self.screenshot() 改为使用 self.last_screenshot,和本次 PR "减少冗余截图"的目标是一致的,整体逻辑看起来与原先等价且少了一次截图 IO。

需要确认的一点是,这个起始节点上 @operation_nodescreenshot_before_round 默认值/配置:

  • 如果该节点在每一轮 check_screen 调用前都会由框架自动截图并写入 last_screenshot,那现在的实现是安全的;
  • 如果默认并不会在每轮前截图(例如默认是 False,或在别处显式把这个节点的 screenshot_before_round 关掉),那这里的 last_screenshot 可能是上一节点甚至上一轮的画面,极端情况下为 None,后续 crop_image / OCR / 模板匹配都会出问题。

建议你确认一下这个节点当前的装饰器配置,必要时可以显式加上 screenshot_before_round=True,或在框架层保证 start node 一定会刷新 last_screenshot,以避免隐蔽回归。

src/sr_od/sr_map/operations/choose_planet.py (1)

34-34: Verify screenshot freshness in retry loop before choosing planet

Changing from self.screenshot() to self.last_screenshot requires confirming that the operation framework guarantees fresh screenshots in the following scenarios:

  1. At operation start: last_screenshot must be current when this start node first executes
  2. Between retries: The screenshot must be refreshed before each retry attempt (up to 10 retries allowed), since the method performs screen state changes (clicks, drags) that require detecting updated visual state

Without verification that the framework updates last_screenshot at these critical points, the method risks using stale screenshots for planet detection, causing incorrect navigation or retry exhaustion.

Confirm the screenshot update mechanism in:

  • Operation framework initialization before start nodes
  • Retry loop execution between attempts
  • Whether screenshot_before_round or similar mechanism applies
src/sr_od/app/sim_uni/operations/sim_uni_enter_fight.py (1)

82-84: Verify that operation_node framework guarantees fresh screenshots before each round.

This change replaces self.screenshot() with self.last_screenshot, which aligns with the PR's goal of reducing redundant screenshots. However, this method uses the screenshot for critical real-time operations: game state detection (line 92-94), attack logic (line 103), and non-world state handling (line 107).

Since the decorator does not specify screenshot_before_round=False, the framework should automatically update self.last_screenshot before the operation executes. Confirm that:

  1. The operation_node framework guarantees last_screenshot is refreshed before each round by default
  2. This guarantee applies to nodes with is_start_node=True
  3. No other self.screenshot() calls remain in this method that would indicate inconsistent screenshot sourcing

Without this confirmation, the change risks using stale screenshot data for critical game state decisions.

src/sr_od/operations/cancel_mission_trace.py (1)

47-47: 验证操作节点间的截图更新时机

self.screenshot() 改为 self.last_screenshot 会影响截图的时效性。_cancel_trace 方法需要基于上一个节点 _try_open_mission 点击后的屏幕状态来判断:

  • 第48行:is_normal_in_world 检查是否仍在大世界
  • 第52行:round_by_find_and_click_area 查找并点击"停止追踪"按钮

请确认操作框架在执行 _try_open_mission 的点击操作后、进入 _cancel_trace 之前,会自动更新 last_screenshot。如果 last_screenshot 保存的是点击前的旧截图,可能导致误判或找不到按钮。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (3)

146-189: 确认 Line 154 保留 screenshot() 的设计意图

注意到 _detect_screen 方法在 Line 154 仍然使用 self.screenshot(),而不是 self.last_screenshot。这与本次 PR 的整体方向不一致。

由于该方法在 Line 152 先调用了 self._view_down() 改变视角,因此 Line 154 需要获取视角调整后的新截图进行 YOLO 检测。如果是这个原因,当前实现是正确的。

请确认这是有意为之,还是遗漏了修改。


43-52: Verify start node last_screenshot initialization

before_route is decorated as a start node (is_start_node=True). When first executed, self.last_screenshot may not be initialized or may contain stale data from a previous operation. Confirm whether the operation framework automatically captures a screenshot before executing start nodes. If not, using last_screenshot here could cause check_angle(screen) to fail with invalid or missing data.


70-87: 验证缓存截图对战斗检测的时效性影响

在战斗场景中,使用 self.last_screenshot 可能导致系统基于过期信息做出判断。特别是在快速变化的战斗环境中(如敌人移动、被锁定提示等),延迟可能影响检测准确性。

建议确认:

  1. 操作框架是否在每轮执行前自动截图,确保 last_screenshot 足够新鲜
  2. 缓存截图的时间间隔是否会影响小地图红点检测和屏幕状态识别的准确性
  3. 如有必要,在装饰器中添加 screenshot_before_round=False 以获取实时截图
src/sr_od/app/sim_uni/operations/move_v1/move_to_next_level.py (3)

121-121: 确认循环执行中的截图刷新策略。

此方法通过 round_wait() 多次循环执行,期间涉及时间敏感的状态检测:

  • Line 123: 战斗状态检测
  • Line 155: 入口检测(视角旋转后需要新截图)
  • Line 234: 交互提示检测(移动后需要实时状态)

如果 self.last_screenshot 在每次 round 循环开始前未自动刷新,可能导致:

  1. 移动后交互提示已出现,但使用旧截图未能检测到
  2. 已进入战斗,但旧截图显示仍在大世界
  3. 视角旋转后入口已可见,但旧截图显示未找到入口

请确认操作框架在每次 round_wait() / round_retry() 返回后重新执行节点前,是否会自动刷新 self.last_screenshot


261-261: 验证节点间截图的时效性。

此节点在 _move_and_interact 完成后执行,用于检测并点击精英层的确认按钮。使用 self.last_screenshot 需要确认:

  • 节点切换时(从 _move_and_interact_confirm)是否会自动刷新截图
  • Line 263 的 OCR 识别和点击操作依赖当前屏幕状态,使用过期截图可能导致识别失败

虽然此节点的重试机制(line 269)可以缓解问题,但如果截图未在节点开始时刷新,可能增加不必要的重试次数。


99-99: Verify that self.last_screenshot is populated before start node execution.

As a start node (is_start_node=True), confirm the operation framework refreshes self.last_screenshot before executing this node. If the framework doesn't guarantee this, the cached screenshot may be None or stale, causing:

  • Line 101: combat detection to fail or miss active combat state
  • Line 112: mini map analysis to use incorrect data

Verify the timing guarantee in the operation framework's operation_node decorator and round execution logic.

src/sr_od/sr_map/operations/choose_region.py (3)

46-46: Verify that last_screenshot is properly refreshed on retry before using it in retry logic.

This change replaces self.screenshot() calls with self.last_screenshot in methods that use retry logic (round_retry()). The safety of this change depends on whether the operation framework captures a fresh screenshot before each retry attempt. Verify that the @operation_node decorator ensures last_screenshot is updated to the current state when round_retry() is called, especially in _choose_sub_region which has node_max_retry_times=20 and performs multiple UI interactions (clicks, scrolls, drags). If last_screenshot is not refreshed on retry, the logic may operate on stale screenshots.


194-220: 验证高频重试场景下截图更新的正确性。

此方法允许最多 20 次重试,且包含多个会改变屏幕状态的 UI 交互(拖动地图、点击子区域入口)。关键问题:

  1. Line 196 的 _in_sub_region(screen) 检测依赖当前屏幕状态,如果 screen 为旧截图会误判
  2. Line 199 的地图匹配依赖当前地图位置,拖动后使用旧截图会计算错误偏移
  3. 基于错误偏移计算的点击坐标可能点击错误位置

需要验证操作节点在重试时的截图刷新机制,特别是 last_screenshot 何时更新以及每次重试是否捕获新截图。


57-92: Verify screenshot refresh timing between operation retries.

This method returns round_retry(wait=1) after clicking (line 78) or scrolling (line 89). The concern is whether the framework captures a fresh screenshot before re-invoking this operation on the next cycle. If last_screenshot is stale, OCR on line 64 and region matching on lines 72–78 could read incorrect UI state, potentially causing wrong selections or infinite retries.

Confirm with the operation framework that last_screenshot is updated by the framework before each operation invocation, not reused across retry cycles.

src/sr_od/app/sim_uni/operations/curio/sim_uni_choose_curio.py (4)

179-213: 确认后的状态判断依赖最新截图

该方法在点击确认后判断画面状态(line 185-192),需要准确识别当前是否仍在选择奇物页面(line 199),或已转换到其他状态(祝福、丢弃奇物等)。使用 last_screenshot 可能无法捕捉到点击后的最新UI状态,导致:

  1. Line 199 误判为仍在选择奇物页面
  2. 错误地递减 curio_cnt_type(line 203)
  3. 进入错误的控制流分支

建议验证在方法执行时 last_screenshot 是否已更新为点击确认后的最新画面。


255-267: 起始节点的屏幕状态检测需要最新截图

作为操作的起始节点,该方法使用 last_screenshot 进行状态检测(line 262)。如果 last_screenshot 不是进入该操作时的最新截图,可能导致:

  1. 无法正确检测到丢弃奇物页面(line 262)
  2. 触发不必要的重试(line 267)
  3. skip_first_screen_check 优化逻辑冲突(line 258-260直接假设状态正确)

需要确认起始节点执行时 last_screenshot 是否已更新。


272-283: OCR识别依赖准确的截图

该方法使用 last_screenshot 进行奇物的OCR识别(通过 _get_curio_pos 在line 275调用),并基于识别结果计算点击位置(line 280)。使用过期的截图可能导致:

  1. OCR识别到错误的奇物名称
  2. 点击位置计算错误(尤其当 curio_cnt_type 变化导致使用不同的RECT列表时)
  3. 重试逻辑(line 270,从'确认'节点返回)中使用了错误的基准截图

这与 SimUniChooseCurio._choose_curio 存在相同的风险。


79-95: 验证 last_screenshot 的时效性

该方法使用 self.last_screenshot 替代了 self.screenshot(),依赖缓存的截图进行屏幕状态检测(line 84)和OCR识别奇物(通过 _get_curio_pos 调用)。如果 last_screenshot 不是最新的,可能导致:

  1. 识别到错误的奇物
  2. 点击错误的位置
  3. 屏幕状态判断失败

请确认 @operation_node 装饰器是否会在方法执行前自动更新 last_screenshot,以保证截图的时效性。

src/sr_od/challenge_mission/choose_support_in_team.py (6)

50-59: 代码看起来不错

click_support 节点使用 last_screenshot 是合理的,因为它依赖于上一个节点的执行,框架应该已经在本轮开始时捕获了新截图。


90-99: 代码看起来不错

click_join 节点使用 last_screenshot 是合理的,该节点在点击头像后执行,框架应该已经捕获了新截图。


146-155: 代码看起来不错

wait_at_last 节点使用 last_screenshot 是合理的,作为终止节点,它在等待画面加载完成,框架应该在每轮重试时捕获新截图。


101-144: 保留了防御性代码,值得肯定

_get_character_pos 方法在第 107 行保留了当 screen 参数为 None 时调用 self.screenshot() 的后备逻辑。虽然在正常流程中不会触发(因为调用时总是传入 last_screenshot),但这是良好的防御性编程实践。


34-48: Screenshot availability in start nodes requires framework verification

The concern about last_screenshot availability in the start node (is_start_node=True at line 39) cannot be verified without access to the operation framework implementation. The review question is valid: if the framework does not automatically capture and store a screenshot before executing start nodes, using self.last_screenshot instead of self.screenshot() will cause recognition failures when the node first executes and last_screenshot is None or stale.

This requires confirmation that:

  • The operation framework captures a screenshot before executing start nodes and stores it in last_screenshot
  • OR the framework ensures last_screenshot is properly initialized for start nodes before their execution

Without this guarantee, start node screenshots should use self.screenshot() to ensure a fresh capture is available.


62-88: 验证拖动操作后的截图更新

click_avatar 方法在第 81 行执行拖动操作来滚动支援角色列表,然后在第 82 行以 wait=2 进行重试。需要确认重试时框架是否会捕获新截图以识别滚动后的内容,以及 last_screenshot 在下一轮开始时是否会更新为拖动后的画面。

如果重试时使用的仍是拖动前的旧截图,_get_character_pos() 调用将无法识别新滚动出来的角色。请检查 round_retry() 方法的实现和 operation_node 装饰器的执行逻辑,确认截图的更新时机。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_move_to_next_level_v3.py (6)

103-103: 改动合理

作为操作节点,使用 self.last_screenshot 应该能获取到框架提供的最新截图,适合用于小地图角度分析。


132-132: 改动合理

特征匹配依赖准确的屏幕状态,作为操作节点应该能获取到最新的截图。


156-163: 正确处理了截图时机

第 156 行使用 self.last_screenshot 获取初始屏幕状态是合理的。更重要的是,第 163 行在 sleep(1) 之后仍然保留了显式的 self.screenshot() 调用,这是正确的做法,因为休眠后屏幕状态已经发生变化,需要获取最新的截图。这体现了对截图时机的准确把握。


181-181: 改动合理

移动前的交互词识别使用 self.last_screenshot 是合理的,框架应该会在节点执行前提供最新截图。


321-321: 改动合理

确认操作对时间敏感度较低,使用 self.last_screenshot 是合理的。


74-74: Verify that self.last_screenshot is guaranteed to be fresh at operation_node entry

This method relies on YOLO detection with accurate screen state. Confirm the operation_node framework updates self.last_screenshot before each node execution to ensure detection accuracy remains unaffected.

src/sr_od/operations/move/move_without_pos.py (1)

42-51: Verify that last_screenshot is valid when start node executes

The _turn method is marked with is_start_node=True, designating it as the operation's entry point. The refactored code now uses self.last_screenshot instead of self.screenshot() for mini-map angle analysis, which directly affects turning direction accuracy.

Confirm that the operation framework guarantees a fresh, valid screenshot before executing start nodes. If there is no such guarantee, refactor to explicitly call self.screenshot() to ensure the mini-map analysis receives current screen state.

src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_bless.py (1)

145-165: Verify the inconsistent parameter order in in_sim_uni_choose_bless calls

The function is called with different parameter orders in the same file:

  • in_sim_uni_choose_bless(self.ctx, screen) at wait_not_in_bless (lines 145-165)
  • in_sim_uni_choose_bless(screen, self.ctx.ocr) at first_wait

This inconsistency needs to be resolved by checking the actual function signature and ensuring all call sites use the correct parameter order and types. Update both calls to match the intended signature and the rest of the codebase.

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_elite_route_v2.py (1)

44-53: 统一改用 last_screenshot 的改动整体是合理的

  • 这三个节点(before_route_check_red_detect_reward)的 @operation_node 都没有显式设置 screenshot_before_round,按照当前基类实现默认是 True,框架在每轮节点执行前会先自动截图并写入 self.last_screenshot。在这种前提下,把节点内部的 self.screenshot() 替换为复用 self.last_screenshot,只去掉了同一轮内的重复截图,对逻辑没有实质性改变,同时能减少一次屏幕抓取的开销。
  • _detect_reward 往往在 _after_fight 后执行,而 _after_fight 内部会调整视角;由于“识别沉浸奖励”节点自身也会在进入前自动截图,所以这里的 last_screenshot 应该是调整视角之后的画面,这一点与之前“在节点体内再手动 self.screenshot()”的效果保持一致。
  • 小提醒(非阻塞):如果将来在单测或调试中有直接调用这些节点方法(绕过 Operation.execute() / 节点调度)的用法,需要事先保证 last_screenshot 已经被填充,否则传入 None 给小地图/YOLO 相关函数可能会抛异常。现在的代码假设节点总是通过框架正常运行,这点可以在使用文档或测试里约定清楚即可。

Also applies to: 55-72, 125-163

src/sr_od/operations/back_to_normal_world_plus.py (1)

26-108: 改用 last_screenshot 的前提假设需要确认

这里改为直接使用 self.last_screenshot,能避免在节点内重复截屏,整体逻辑与之前一致,看起来是本 PR 期望的方向。

唯一需要确认的一点是:当前 operation 框架是否在每一轮进入节点前,都会统一刷新一次 last_screenshot。如果没有统一刷新,而是依赖各节点自己调用 self.screenshot(),那这次改动可能会在某些轮次上使用到过期画面,影响状态判断。

建议你确认下基类 SrOperation / 调度器对 last_screenshot 的维护策略,确保这一节点在每轮执行前都能拿到最新画面;如果是的,那这个改动就是安全的性能优化。

src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_mm.py (1)

57-103: 使用 last_screenshot 与全局改动保持一致

这里将 _execute_one_round 内部的截屏改为复用 self.last_screenshot,配合统一的截屏机制,可以减少重复 I/O,逻辑本身未改变。

在前一个评论中已经提到:前提是框架在每轮进入节点前会刷新 last_screenshot。在该前提成立的情况下,这里改动是合理且推荐的。

src/sr_od/app/sim_uni/operations/auto_run/reset_sim_uni_level.py (1)

32-45: 节点内复用 last_screenshot 合理

_temp_leave 现在直接用 self.last_screenshot 做区域识别,配合统一截屏逻辑,可以避免节点内多余的 screenshot() 调用,成功/失败分支控制流均保持不变。

在框架统一刷新 last_screenshot 的前提下,这里改动没有风险。

src/sr_od/sr_map/operations/choose_floor.py (1)

25-47: 选择楼层逻辑保持不变,仅复用缓存截图

check_screen 改为使用 self.last_screenshot,后续获取当前楼层、点击目标楼层、返回成功/重试的逻辑完全不变;在统一截屏机制下,这里只是去掉重复截屏,改动安全。

src/sr_od/operations/wait/wait_in_world.py (1)

29-35: 等待主界面的状态判断改为复用 last_screenshot

这里用 self.last_screenshot 判断是否进入大世界主界面,成功后等待 wait_after_success,否则 1 秒后重试,控制流完全一致。结合统一截屏,这里去掉节点内的额外截屏是合理的。

src/sr_od/app/world_patrol/world_patrol_enter_fight.py (1)

68-94: 进入战斗主循环改为使用缓存截图

run 现在用 self.last_screenshot 作为本轮画面,后续状态机(大世界 / 战斗 / 未知)以及 _try_attack / _handle_not_in_world 的调用路径都未改动。

只要调度器在每轮 node 调用前更新 last_screenshot,这里就是纯粹减少重复截屏的优化。

src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_interact_by_detect.py (1)

68-85: 移动到交互目标节点改为复用最后一帧截图

move 使用 self.last_screenshot 后,后续 _check_interact_wordhandle_in_world 中的 OCR 与 YOLO 检测仍基于同一帧,逻辑顺序保持不变;interact_during_move 分支的在/不在大世界判断也仍然可靠。

在统一由框架刷新 last_screenshot 的前提下,这里去掉显式 self.screenshot() 调用是合适的。

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_world.py (1)

83-115: 战败结算相关节点统一改为使用 last_screenshot

battle_fail_exitbattle_fail_exit_confirmclick_empty 三个节点都改为基于 self.last_screenshot 做一次性的区域查找并点击,success_wait / retry_wait 等参数和返回语义完全没变。

这与本 PR 其它位置的改法一致,可以避免节点内重复截屏,前提同样是调度器在每轮执行节点前会刷新 last_screenshot

src/sr_od/app/sim_uni/operations/sim_uni_exit.py (3)

37-37: 节点入口使用last_screenshot是合理的。

在操作节点的起始检测中使用last_screenshot是合理的优化,框架应该在节点执行前已经更新了截图。


70-82: 验证操作后截图是否及时更新。

此方法在open_menu后执行(wait=1秒),需要检测菜单打开后的画面。使用last_screenshot的前提是框架在前一个节点的wait完成后重新截图。

如果last_screenshot仍是菜单打开前的画面,将无法正确识别菜单选项。

建议验证在round_success(wait=1)后,下一个节点执行前框架是否更新last_screenshot


86-93: 连续UI操作需要确保截图时效性。

click_confirmclick_empty方法依赖于前序操作完成后的最新画面。如果last_screenshot没有在节点切换时更新,可能导致:

  • 识别不到新出现的确认对话框
  • 点击位置基于旧画面而失效

Also applies to: 97-104

src/sr_od/operations/battle/start_fight_for_elite.py (1)

192-205: 战斗状态判断需要最新的屏幕状态。

_attack方法在使用秘技后执行,需要判断是否已进入战斗。使用last_screenshot存在时序风险:

如果使用秘技后立即触发战斗,但last_screenshot还是触发前的画面,会导致:

  • 误判为"仍在大世界"
  • 重复调用initiate_attack()
  • 可能影响战斗流程

建议验证框架是否在节点切换时自动更新截图,特别是在战斗状态可能快速变化的场景。

src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py (2)

20-33: 起始节点使用last_screenshot合理。

作为起始节点,框架应该在执行前已更新截图,使用last_screenshot是合理的优化。


36-46: UI交互流程依赖节点间的截图更新。

_check_reward_claim_reward方法依次执行,每个方法都需要基于前一步操作后的最新画面。使用last_screenshot的前提是框架在节点切换时自动更新截图。

如果框架行为符合预期,这个变更可以有效减少冗余截图。

建议验证节点切换时框架的截图更新机制,确保每个节点看到的是最新状态。

src/sr_od/app/sim_uni/operations/battle/sim_uni_fight_elite.py (2)

32-49: 敌人检测需要实时画面数据。

_check_enemy使用OCR和YOLO进行敌人检测,对截图时效性要求较高。如果last_screenshot不够新,可能导致:

  • 敌人位置识别滞后
  • 锁定状态检测延迟
  • 影响战斗触发时机

建议确认框架在此节点执行前已更新截图。


58-68: 战斗进入判断的时效性风险。

_fight方法在使用秘技后执行,需要判断是否成功进入战斗。使用last_screenshot可能导致判断延迟,特别是在使用远程攻击角色时,战斗触发可能有延迟。

src/sr_od/operations/team/choose_support.py (2)

40-50: UI识别节点使用last_screenshot合理。

作为起始节点的画面识别,使用last_screenshot是合理的优化。


52-74: 角色选择流程依赖截图更新机制。

choose_supportclick_join构成了选择角色的完整流程。使用last_screenshot的前提是:

  1. 点击头像后,框架更新截图以显示入队按钮
  2. 节点切换时自动刷新画面状态

如果框架行为符合预期,这个变更可以减少不必要的截图操作。

建议验证UI交互流程中节点切换时的截图更新时机。

src/sr_od/app/nameless_honor/nameless_honor_app.py (1)

104-123: 奖励领取后的状态检测需要最新截图。

_check_screen_after_reward在奖励领取后执行,需要检测可能出现的选择奖励弹窗。使用last_screenshot存在时序风险:

前一个节点_claim_reward执行后等待2秒(success_wait=2),这期间画面可能发生变化(弹窗出现)。如果last_screenshot没有在等待后更新,将无法检测到新出现的UI元素。

建议验证框架在success_wait后、下一个节点执行前是否更新last_screenshot

src/sr_od/sr_map/operations/open_map.py (1)

21-21: Verify that the operation_node decorator updates last_screenshot before execution.

The change to use self.last_screenshot at line 21 in the check_screen method is a reasonable optimization for a start node. However, confirmation is needed that the operation_node decorator (with is_start_node=True) guarantees last_screenshot is updated before the node executes, to ensure accurate state detection.

src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_detect.py (1)

44-64: Verify whether last_screenshot is refreshed between operation iterations.

This method is a long-running operation (timeout_seconds=20) that continuously monitors screen state. The review concern is valid: if the framework captures the screenshot only once when the node starts, subsequent loop iterations through round_retry() or round_wait() will operate on stale data, potentially missing:

  • Real-time detection of being locked by enemies
  • Current enemy positions
  • Accurate movement decisions

This needs verification against the framework's operation lifecycle implementation to confirm whether last_screenshot is automatically refreshed on each iteration or remains static throughout the operation's lifetime.

src/sr_od/app/sim_uni/sim_uni_app.py (1)

81-88: 使用 last_screenshot 与节点预截图逻辑保持一致

_check_initial_screen 在节点开始直接使用 self.last_screenshot,结合 operation_node 默认的 screenshot_before_round=True,等价于每轮只截一次图并在本轮内复用,行为与之前在函数内主动截图一致,同时符合本 PR 减少冗余截图的目标。

src/sr_od/operations/store/buy_store_item.py (1)

32-42: 商店购买流程统一改用 last_screenshot,语义保持不变

choose_item / choose_buy_num / buy_confirm / click_empty 四个节点都在方法开始读取一次 self.last_screenshot,配合默认的 screenshot_before_round=True,等价于每轮开始时截图一次并在节点内复用;这些方法在读取 screen 之前没有额外交互操作,因此与之前使用 self.screenshot() 的行为基本一致,只是去掉了重复截图调用。

Also applies to: 66-69, 77-88, 96-98

src/sr_od/app/div_uni/operations/choose_oe_support.py (1)

36-38: 饰品支援选择流程改用 last_screenshot 合理

check_screen / click_support / choose_support 均在节点开始读取 self.last_screenshot,节点本身依然依赖默认的 screenshot_before_round=True 获取当前帧,因此只减少了多余的截图库调用;函数内部在使用 screen 前没有修改画面,round_by_find_areaChooseSupport.click_avatar 的识别时机与之前保持一致。

Also applies to: 47-49, 58-62

src/sr_od/app/relic_salvage/relic_salvage_app.py (1)

29-31: 遗器分解流程多处改用 last_screenshot,与自动截图机制兼容

“快速选择”“选择等级/弃置”“快速选择确认”“点击分解/确认”等节点都在一轮开始时直接使用 self.last_screenshot 做区域匹配和点击,节点内只有一次点击操作,由框架在下一轮进入前重新截图,因此整体时序与之前在节点中主动截图保持一致,只是减少了冗余的截图调用。

Also applies to: 36-40, 45-48, 57-59, 64-66, 71-73

src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py (1)

44-68: 模拟宇宙沉浸奖励相关节点改用 last_screenshot,时序正确

_get_reward / _click_empty / _check_state 都在节点开始使用 self.last_screenshot 进行画面识别,依赖框架在每轮进入节点前自动截图;点击领取奖励、点击空白关闭等操作发生后,下一节点会重新截图,因此各步骤的状态判断仍然基于最新一帧。本次仅去除了节点内部重复截图,对奖励上限判断和状态流转没有功能性影响。

Also applies to: 94-101, 110-115

src/sr_od/operations/technique.py (1)

116-124: 秘技与快速恢复相关节点统一使用 last_screenshot,行为保持不变

  • UseTechnique._check_technique_point / CheckTechniquePoint.check 依然在进入节点前由框架截图,然后用 last_screenshot 识别秘技点数和是否在大世界界面;
  • UseTechnique._use 仅在尚未出现对话框且需检查可用性时读取屏幕文字,之后才调用 use_technique,不会读到被自身操作改变后的旧帧;
  • _confirm / _wait_in_world_to_other 以及 FastRecover.use / wait_in_world / fail_check_screen 在读取 screen 之前没有新的点击或拖动操作,仍然是一轮一截一用;

总体来看,这些改动只是把原先在方法里的 self.screenshot() 替换为对每轮自动截图的复用,在状态机逻辑和画面判断时机上与旧实现一致,同时减少了高频场景中的截图库调用。

Also applies to: 132-135, 153-181, 196-210, 226-235, 263-266, 280-284, 294-301

src/sr_od/app/div_uni/operations/choose_oe_file.py (1)

33-43: 选择存档节点关闭自动截图并保留一次手动截图是合理的

  • check_screen / click_switch_file / wait_management_screen 改为使用 self.last_screenshot,依赖默认的 screenshot_before_round=True,每轮只用框架自动截图一帧进行识别,去掉了函数内重复截图;
  • choose_file 显式设置 screenshot_before_round=False,先根据配置点击目标档案并稍作等待,再手动调用一次 self.screenshot(),随后所有“确认切换/已使用中”的识别都基于这张新截图,避免了在点击前后各截一次图的浪费,也保证了判断发生在 UI 更新之后;
  • 函数内所有使用 screen 的地方都在这次手动截图之后执行,不会出现读取过期帧的情况。

建议实际在游戏内分别测试 1~4 号档案以及“档案使用中”的分支,确认画面切换和按钮识别的时机与预期一致。

Also applies to: 52-55, 63-66, 69-97

src/sr_od/app/daily_training/daily_training_app.py (1)

70-85: 每日实训领取奖励改用 last_screenshot 与其他模块保持一致

claim_reward 通过 self.last_screenshot 获取当前画面做“指南/每日实训”二级 UI 判断以及奖励按钮位置计算,依赖节点进入前的自动截图,不再在方法内额外截图;逻辑与原实现等价,同时与本 PR 在其他模块中的用法保持一致。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (2)

44-47: 统一使用 last_screenshot 获取当前帧,避免重复截图

before_route 和 _check_mm_icon 都改为使用 self.last_screenshot,且在读取 screen 之前没有移动/旋转等改动画面的操作,配合 operation_node 默认的 screenshot_before_round=True,每轮仍然只基于最新一帧做角度与小地图识别,只是去掉了多余的 self.screenshot() 调用,行为是等价的。

Also applies to: 63-76


92-101: _detect_screen 关闭前置截图与内部手动截图逻辑一致

这里给 _detect_screen 的装饰器加上 screenshot_before_round=False,而函数内部在 _view_down() 之后再调用 self.screenshot() 获取 screen,避免了一轮内“先自动截一张、再手动截一张”的浪费,同时保证用于 YOLO 检测的是视角调整后的画面,这个改动是合理的。

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py (1)

37-40: before_route / _check_mm_icon 使用 last_screenshot 的改动是安全的

这两个节点只是读取当前小地图和朝向,没有在读取 screen 前做移动/转向等操作,且装饰器均使用默认 screenshot_before_round=True,因此每轮进入节点前都会更新 last_screenshot。直接复用 self.last_screenshot 可以减少一次重复截图,原有识别语义不变。

Also applies to: 71-82

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_wait_level_start.py (1)

36-49: check_screen 改用 last_screenshot 与重试机制契合

作为起始节点并带有 node_max_retry_times,每轮进入时框架都会截一次图更新 last_screenshot;这里直接复用 self.last_screenshot 来做状态判断即可,去掉内部额外的 self.screenshot(),不会改变分支逻辑。

src/sr_od/app/sim_uni/operations/bless/sim_uni_upgrade_bless.py (1)

45-57: 本类各节点统一改用 last_screenshot,符合操作节点截屏约定

check_left / check_can_upgrade / choose_bless / upgrade / click_empty 均改为从 self.last_screenshot 获取 screen,而这些节点在读取前都只是做状态识别和点击决定,没有先移动视角或触发会立刻刷新的动画;装饰器也沿用默认 screenshot_before_round=True。因此每轮只会在进入节点前截一次图,OCR 与模板匹配仍基于当前帧,原有功能语义保持不变,同时去掉了多处重复的 self.screenshot() 调用。

Also applies to: 75-77, 123-133, 141-150, 167-175

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_level.py (1)

111-118: 楼层类型与路线匹配改用 last_screenshot,避免多余截图

_check_level_type_check_route 现在都直接使用 self.last_screenshot 来做楼层类型与小地图路线匹配,这两个节点在读取前没有进行移动或其他会改变画面的操作,并且节点装饰器仍使用默认 screenshot_before_round=True。这样每轮识别仍基于最新一帧,只是省掉了函数内部的重复截屏,对匹配稳定性没有负面影响。

Also applies to: 133-149

src/sr_od/app/sim_uni/operations/sim_uni_event.py (1)

61-66: SimUniEvent 中四处改用 last_screenshot,与事件状态机逻辑一致

_wait_choose_opt_by_priority_confirm_check_after_confirm 都改为使用 self.last_screenshot 作为当前帧,节点在读取 screen 前没有实际改动画面(只是更新了标志位),且装饰器保持默认 screenshot_before_round=True。因此每轮事件识别、选项 OCR、确定按钮点击以及确定后的页面分流仍然基于同一帧画面,行为与之前一致,同时减少了多次重复截图。

Also applies to: 77-90, 169-179, 242-247

src/sr_od/operations/enter_game/enter_game.py (1)

45-59: EnterGame 多处改用 last_screenshot,与登录状态判断流程兼容

check_screeninput_account_password 以及 choose_other_account 都改为使用 self.last_screenshot 作为当前画面输入,这些节点在读取 screen 前没有进行新的点击或移动,且装饰器保持默认 screenshot_before_round=True,所以每轮进入节点时都会拿到最新一帧。随后真正进入游戏前仍会重新截一张图传给 round_by_find_and_click_area 做最终按钮点击,整体登录与换号流程的语义不变,只是去掉了多余的 self.screenshot()

Also applies to: 119-148, 161-163

src/sr_od/interastral_peace_guide/open_guide.py (2)

19-23: 确认起始节点的截图初始化

作为起始节点(is_start_node=True),该方法依赖 self.last_screenshot 进行画面识别。请确认操作框架在执行起始节点前会自动初始化 last_screenshot,否则可能导致 None 引用错误。


46-52: 等待循环中使用缓存截图可能导致状态检测延迟

该方法在等待加载循环中使用 self.last_screenshot 检测 UI 是否已加载完成。如果 last_screenshot 未在每次重试前更新,将会检测到过时的屏幕状态,可能导致:

  • 无法及时发现加载完成
  • 等待时间不必要地延长
  • 重试逻辑失效

请确认操作框架是否在每次节点重试执行前自动刷新 last_screenshot

src/sr_od/interastral_peace_guide/guide_transport.py (1)

48-71: 重试循环中的截图时效性问题

该等待节点设置了最多20次重试(node_max_retry_times=20),使用 self.last_screenshot 检测加载完成状态。如果每次重试时 last_screenshot 没有更新,会导致:

  • 重复检测同一张过时截图
  • 即使屏幕已加载完成也无法检测到
  • 可能耗尽所有重试次数后失败

建议验证操作框架是否在每次重试前自动刷新截图,或考虑在关键等待节点使用 self.screenshot() 获取实时状态。

src/sr_od/interastral_peace_guide/guide_choose_category.py (2)

29-36: 等待画面加载时的截图更新机制

该起始节点在等待画面加载时(最多重试5次)使用 self.last_screenshot 检测当前UI状态。与其他文件中的类似等待循环一样,需要确认每次重试前截图是否更新,以避免重复检测过时的屏幕状态。


38-60: OCR 操作的截图新鲜度

该方法使用 self.last_screenshot 进行 OCR 识别和点击操作。虽然前置的 wait_screen 节点应该已确保屏幕稳定,但如果在两个节点之间存在状态变化(如动画、加载),可能影响识别准确性。建议验证节点间截图更新策略是否满足需求。

src/sr_od/interastral_peace_guide/guide_check_power.py (1)

42-44: Verify screenshot freshness guarantees for OCR accuracy

The check_power method relies on self.last_screenshot for OCR-based detection of power and quantity values. Confirm whether:

  1. The @operation_node decorator automatically refreshes last_screenshot before executing each node
  2. The screen state remains stable between choose_guide_tab completion and check_power execution

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sr_od/challenge_mission/use_trailblaze_power.py (1)

1-1: 移除未使用的 PIL.ImageChops.screen 导入,避免 F811 警告

Ruff 报的 F811(“Redefinition of unused screen from line 1”)来自这里:本文件中没有地方把 PIL.ImageChops.screen 当函数调用,screen 只作为局部变量保存截图使用,因此这个导入是未使用且被后面的局部变量反复重定义。

建议直接删除该导入,既能消掉 F811,也避免命名冲突:

-from PIL.ImageChops import screen

如果以后确实需要 ImageChops.screen,再用别名导入(例如 from PIL import ImageChops as _ImageChops)以避免和局部变量同名。

♻️ Duplicate comments (1)
src/sr_od/sr_map/operations/choose_planet.py (1)

34-34: 验证重试时的截图刷新机制(同样的问题)

choose_special_point.py 相同,此方法也从 self.screenshot() 改为 self.last_screenshot,且包含重试逻辑(第 76 行)。

特别关注第 71-76 行的逻辑:

  • 当前屏幕未找到目标星球时,执行拖动操作滚动星球列表
  • 然后返回 round_retry 期望在下次迭代中看到不同的星球
  • 如果 last_screenshot 在重试时未刷新,拖动后仍会看到相同的星球列表,导致重复无效拖动

需要确认框架在重试前是否刷新 last_screenshot

🧹 Nitpick comments (8)
src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (1)

55-60: 确认“确认命途”节点使用缓存截图不会引入时序问题

_confirm_path 也改为使用 self.last_screenshot,避免再次截图,本身是对 PR 目标一致的优化。

但与上一个节点不同,这里直接做一次 round_by_find_and_click_area,没有额外的页面状态检查;它通常紧跟在“选择命途”之后,如果两节点之间 UI 有渐变/弹窗变化,而框架又没有在本节点开始前自动更新 last_screenshot,就可能出现:

  • 之前行为:self.screenshot() 拿到的是“确认命途”按钮已经出现后的最新画面。
  • 现在行为:沿用上一个节点的截图,按钮可能尚未出现,导致多次重试点击失败。

建议你在实际环境下验证一遍:在路径切换较慢、或窗口切出/切回等边缘情况下,这个节点依然能稳定识别并点击确认按钮。如果后续发现时序比较敏感,这里可以单独保守些继续用一次主动截图,或者在装饰器上显式要求本轮前必须截图。

src/sr_od/app/sim_uni/operations/sim_uni_exit.py (1)

71-82: click_exit 使用上一轮截图,需要确认与菜单弹出时机的一致性

这里不再主动截屏,而是依赖进入该节点前由装饰器更新的 last_screenshot。逻辑本身保持不变,area_list 与点击流程也未改动。

建议确认:

  • open_menu 对应的 operation_node 没有设置 screenshot_before_round=False,且状态机在从“打开菜单”跳转到“点击结算”前,会开启新一轮并完成截图;
  • 不存在中途额外等待或动画导致菜单完全展开晚于截图时刻的情况,否则可能会出现误判找不到区域。

如果上述流程在其它模块里已经统一调整过,可以在后续清理中顺带补一条注释说明依赖的是“当前轮截图”,方便以后维护。

src/sr_od/challenge_mission/choose_support_in_team.py (1)

61-89: 头像识别复用同一帧截图,行为更一致

这里将 screen 设为 self.last_screenshot 并传入 _get_character_pos(screen)

  • 头像定位与后续点击使用同一帧画面,比之前在 _get_character_pos 里再单独截图更一致,也避免了单轮内的多次截图。
  • 当找不到角色并执行拖动后,下一轮重试时会重新进入节点(通常会重新截图并更新 last_screenshot),滚动后的画面也能被使用,流程上是自洽的。

如果将来确认所有调用方都能保证传入 screen,可以考虑在 _get_character_pos 里移除内部 self.screenshot() 作为冗余兜底,但这属于后续小优化,不是本 PR 必须修改的内容。

src/sr_od/challenge_mission/use_trailblaze_power.py (4)

103-124: confirm_after_click_challenge 改用 last_screenshot,需确保节点前会自动截图

这里从 self.screenshot() 改成了复用 self.last_screenshot,逻辑上没变,但多了一个前置条件:进入本节点前,框架必须已经刷新过 last_screenshot(通常是 @operation_node 默认的 screenshot_before_round=True 帮你调过一次截图)。

如果以后有人在装饰器上加了 screenshot_before_round=False,这里就会拿到上一个节点的截图(比如“点击挑战”那一拍),有一定风险找不到刚弹出的对话框。

建议:

  • 确认当前这个节点的 operation_node 没有关闭自动截图,且未来也不打算关;
  • 可选兜底:在 last_screenshot 为空时退回到主动截图,增强鲁棒性,例如:
-        screen = self.last_screenshot
+        screen = self.last_screenshot or self.screenshot()

150-176: _after_start_challenge 同样依赖装饰器刷新 last_screenshot

这里与上面的 confirm_after_click_challenge 类似,从主动截图改为使用 self.last_screenshot,可以减少一次截图,但前提是本节点开始前 last_screenshot 已被自动更新。

同样建议:

  • 确认该节点的 @operation_node(name='开始挑战后') 没有配置 screenshot_before_round=False
  • 如果担心未来有人调整装饰器,可以考虑加一个 or self.screenshot() 的兜底,保持行为稳定。

181-223: 战斗结果阶段 last_screenshot 的时效性需要确认

这里 _wait_battle_result 里仍然是主动 self.screenshot() 轮询状态,而 _after_battle_result 改成了使用 self.last_screenshot

def _wait_battle_result(...):
    screen = self.screenshot()
    ...

def _after_battle_result(...):
    screen = self.last_screenshot
    ...

行为是否等价取决于框架流程:

  • 如果进入 _after_battle_result 前,operation_node 会再自动截图一次并写回 last_screenshot,那现在只是去掉了一次冗余截图,逻辑保持不变;
  • 如果该节点现在或将来配置 screenshot_before_round=False,那这里拿到的是 _wait_battle_result 最后一帧的截图,而不是“等待 1 秒后”的当前画面,存在 UI 动画尚未结束、按钮位置变化等小概率风险。

建议:

  1. 明确 _after_battle_result 的装饰器是否保证每轮进入前一定会刷新 last_screenshot
  2. 如果希望更稳健,可以同样加兜底:
-        screen = self.last_screenshot
+        screen = self.last_screenshot or self.screenshot()

另外,从“减少冗余截图”的角度看,后续也可以评估 _wait_battle_result 是否有机会改成依赖框架的自动截图,以进一步统一截图策略(非本次 PR 必做)。


224-241: confirm_after_challenge_again 与首次确认逻辑一致,注意保持截图来源一致性

这里也改为使用 self.last_screenshot,与 confirm_after_click_challenge 一致,有利于减少重复截图并统一写法。

同样的注意点:

  • 需要保证该节点在每轮执行前,last_screenshot 已由框架或上游节点刷新;
  • 如担心未来有人调整装饰器配置,建议加一行兜底逻辑以避免拿到过期截图:
-        screen = self.last_screenshot
+        screen = self.last_screenshot or self.screenshot()

整体逻辑(优先处理开拓力弹框,其次阵亡弹框,否则重试)保持不变,行为风险主要集中在截图时效性这一点上。

src/sr_od/app/assignments/assignments_app.py (1)

92-94: 点击空白区域继续复用 last_screenshot,建议顺带补充返回类型注解

这里点击“委托-点击空白区域继续”时使用 self.last_screenshot 作为输入,与前面几个节点风格统一;另外该方法目前没有 -> OperationRoundResult 标注,可以考虑补上以便类型检查和可读性更一致,例如:

-    def _click_empty(self):
+    def _click_empty(self) -> OperationRoundResult:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2830ab and ddac058.

📒 Files selected for processing (62)
  • src/sr_od/app/assignments/assignments_app.py (3 hunks)
  • src/sr_od/app/daily_training/daily_training_app.py (1 hunks)
  • src/sr_od/app/div_uni/operations/choose_oe_file.py (3 hunks)
  • src/sr_od/app/div_uni/operations/choose_oe_support.py (3 hunks)
  • src/sr_od/app/div_uni/operations/ornamenet_extraction.py (4 hunks)
  • src/sr_od/app/echo_of_war/challenge_ehco_of_war.py (4 hunks)
  • src/sr_od/app/nameless_honor/nameless_honor_app.py (1 hunks)
  • src/sr_od/app/relic_salvage/relic_salvage_app.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/reset_sim_uni_level.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_level.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_world.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/auto_run/sim_uni_wait_level_start.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/battle/sim_uni_fight_elite.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_bless.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_drop_bless.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_upgrade_bless.py (5 hunks)
  • src/sr_od/app/sim_uni/operations/curio/sim_uni_choose_curio.py (4 hunks)
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/move_to_next_level.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_detect.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_mm.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_interact_by_detect.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_move_to_next_level_v3.py (7 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (2 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_elite_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_enter_fight.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_event.py (4 hunks)
  • src/sr_od/app/sim_uni/operations/sim_uni_exit.py (4 hunks)
  • src/sr_od/app/sim_uni/sim_uni_app.py (1 hunks)
  • src/sr_od/app/world_patrol/world_patrol_enter_fight.py (1 hunks)
  • src/sr_od/challenge_mission/choose_support_in_team.py (5 hunks)
  • src/sr_od/challenge_mission/use_trailblaze_power.py (4 hunks)
  • src/sr_od/interastral_peace_guide/guide_check_power.py (1 hunks)
  • src/sr_od/interastral_peace_guide/guide_choose_category.py (2 hunks)
  • src/sr_od/interastral_peace_guide/guide_transport.py (1 hunks)
  • src/sr_od/interastral_peace_guide/open_guide.py (2 hunks)
  • src/sr_od/operations/back_to_normal_world_plus.py (1 hunks)
  • src/sr_od/operations/battle/start_fight_for_elite.py (1 hunks)
  • src/sr_od/operations/cancel_mission_trace.py (1 hunks)
  • src/sr_od/operations/click_dialog_confirm.py (1 hunks)
  • src/sr_od/operations/enter_game/enter_game.py (3 hunks)
  • src/sr_od/operations/enter_game/switch_account.py (1 hunks)
  • src/sr_od/operations/interact/catapult.py (1 hunks)
  • src/sr_od/operations/interact/move_interact.py (1 hunks)
  • src/sr_od/operations/move/move_directly.py (1 hunks)
  • src/sr_od/operations/move/move_without_pos.py (1 hunks)
  • src/sr_od/operations/store/buy_store_item.py (4 hunks)
  • src/sr_od/operations/synthesize/synthesize.py (7 hunks)
  • src/sr_od/operations/team/check_team_members_in_world.py (1 hunks)
  • src/sr_od/operations/team/choose_support.py (3 hunks)
  • src/sr_od/operations/team/switch_member.py (1 hunks)
  • src/sr_od/operations/technique.py (8 hunks)
  • src/sr_od/operations/wait/wait_in_world.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_floor.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_planet.py (1 hunks)
  • src/sr_od/sr_map/operations/choose_region.py (3 hunks)
  • src/sr_od/sr_map/operations/choose_special_point.py (1 hunks)
  • src/sr_od/sr_map/operations/open_map.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/sr_od/operations/team/check_team_members_in_world.py
🚧 Files skipped from review as they are similar to previous changes (36)
  • src/sr_od/operations/technique.py
  • src/sr_od/operations/store/buy_store_item.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_respite_route_v2.py
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_bless.py
  • src/sr_od/operations/cancel_mission_trace.py
  • src/sr_od/operations/move/move_without_pos.py
  • src/sr_od/operations/move/move_directly.py
  • src/sr_od/sr_map/operations/choose_region.py
  • src/sr_od/operations/synthesize/synthesize.py
  • src/sr_od/sr_map/operations/choose_floor.py
  • src/sr_od/app/sim_uni/operations/curio/sim_uni_choose_curio.py
  • src/sr_od/app/sim_uni/sim_uni_app.py
  • src/sr_od/sr_map/operations/open_map.py
  • src/sr_od/interastral_peace_guide/guide_check_power.py
  • src/sr_od/app/sim_uni/operations/auto_run/reset_sim_uni_level.py
  • src/sr_od/app/sim_uni/operations/sim_uni_event.py
  • src/sr_od/operations/team/switch_member.py
  • src/sr_od/app/sim_uni/operations/move_v1/move_to_next_level.py
  • src/sr_od/operations/team/choose_support.py
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_mm.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py
  • src/sr_od/operations/interact/move_interact.py
  • src/sr_od/operations/enter_game/enter_game.py
  • src/sr_od/interastral_peace_guide/open_guide.py
  • src/sr_od/operations/battle/start_fight_for_elite.py
  • src/sr_od/app/sim_uni/operations/bless/sim_uni_drop_bless.py
  • src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_enemy_by_detect.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_move_to_next_level_v3.py
  • src/sr_od/app/div_uni/operations/choose_oe_support.py
  • src/sr_od/interastral_peace_guide/guide_transport.py
  • src/sr_od/app/sim_uni/operations/sim_uni_enter_fight.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_elite_route_v2.py
  • src/sr_od/operations/wait/wait_in_world.py
  • src/sr_od/app/relic_salvage/relic_salvage_app.py
  • src/sr_od/app/div_uni/operations/choose_oe_file.py
  • src/sr_od/app/daily_training/daily_training_app.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-13T12:07:30.874Z
Learnt from: JoshCai233
Repo: OneDragon-Anything/StarRailOneDragon PR: 521
File: src/sr_od/app/sim_uni/sim_uni_app.py:61-87
Timestamp: 2025-10-13T12:07:30.874Z
Learning: 在 `_check_points_reward()` 中,`OperationRoundResult` 的语义与常规不同:`FAIL` 表示"奖励未完成,需要继续执行自动化",而 `SUCCESS` 和 `RETRY` 表示"提前返回,跳过自动化"。这是因为调用方使用 `if result != FAIL: return` 来判断是否跳过后续的自动化脚本执行。

Applied to files:

  • src/sr_od/app/assignments/assignments_app.py
  • src/sr_od/sr_map/operations/choose_special_point.py
  • src/sr_od/operations/click_dialog_confirm.py
  • src/sr_od/operations/enter_game/switch_account.py
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py
  • src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py
  • src/sr_od/challenge_mission/use_trailblaze_power.py
  • src/sr_od/app/echo_of_war/challenge_ehco_of_war.py
  • src/sr_od/operations/back_to_normal_world_plus.py
  • src/sr_od/operations/interact/catapult.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py
🧬 Code graph analysis (5)
src/sr_od/app/assignments/assignments_app.py (1)
src/one_dragon/base/operation/operation.py (2)
  • round_by_ocr_and_click (919-998)
  • round_by_find_and_click_area (789-857)
src/sr_od/operations/enter_game/switch_account.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py (1)
src/one_dragon/base/operation/operation.py (2)
  • round_by_find_and_click_area (789-857)
  • status (51-55)
src/sr_od/operations/interact/catapult.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
🪛 Ruff (0.14.7)
src/sr_od/challenge_mission/use_trailblaze_power.py

110-110: Redefinition of unused screen from line 1

(F811)


157-157: Redefinition of unused screen from line 1

(F811)


211-211: Redefinition of unused screen from line 1

(F811)


227-227: Redefinition of unused screen from line 1

(F811)

🔇 Additional comments (38)
src/sr_od/app/world_patrol/world_patrol_enter_fight.py (1)

70-70: 使用 last_screenshot 的前置条件需要明确

这里改为直接使用 self.last_screenshot,能减少重复截图调用,逻辑上与原先 screen = self.screenshot() 一致,改动本身是合理的。

需要确认两点:

  1. 在每轮进入该 operation_noderun() 之前,框架是否始终保证当前帧已经更新 self.last_screenshot(尤其是首轮或从挂起恢复后的第一轮);
  2. 若存在极端情况下 last_screenshot 可能为 None 或旧帧,是否需要在此处做一个兜底,例如:
screen = self.last_screenshot or self.screenshot()

如果在基类里已经严格保证了非空且为最新帧,可在注释或文档中简单注明这一约定,以免后续维护者误用。

src/sr_od/operations/enter_game/switch_account.py (1)

27-27: 在节点上关闭回合前自动截图的影响需要业务上确认

这里给 _back_to_login 节点显式加上 screenshot_before_round=False,结合函数内部在行 41 已经手动调用了一次 self.screenshot(),可以确实减少一张冗余截图,逻辑上看起来是安全的。

建议你再确认两点:

  • 是否有地方依赖该节点“回合开始自动截图”的副作用(例如依赖回合前的 last_screenshot 做统计/日志);
  • 与本 PR 其它改为使用 last_screenshot 的节点在行为上是否保持一致(比如这里是否也有机会改为复用上一次截图,而不是再拍一张)。

如果以上都确认无依赖,这个改动本身是可以接受的。

src/sr_od/app/sim_uni/operations/bless/sim_uni_choose_path.py (1)

33-53: 使用 last_screenshot 的前置条件需要确认

这里从主动调用 self.screenshot() 改成拿 self.last_screenshot,整体逻辑(先校验页面,再 OCR 点击,再不成功时拖动重试)保持不变,减少了一次截图开销,这个方向是合理的。

需要确认的一点是:对这个节点来说,operation_node 装饰器是否保证在每轮开始前都会刷新一次截图并写入 last_screenshot(即默认 screenshot_before_round=True,且本节点没有在别处被改成 False)。如果存在绕过该机制直接调用节点、或以后修改默认值的情况,这里可能会用到过期截图,导致误判“未在模拟宇宙命途页面”。

建议:

  • 确认当前框架行为 + 跑一遍实际流程验证该节点在多轮重试时 screen 始终是最新的。
  • 如果以后考虑调整默认截图策略,可以在这类强依赖当前画面的节点上显式标注 screenshot_before_round=True 以免被波及。
src/sr_od/operations/interact/catapult.py (1)

57-71: 关闭自动截图以避免重复截图,逻辑保持不变

这里通过在装饰器中显式设置 screenshot_before_round=False,关闭了框架在轮次开始前的自动截图,而方法内部仍然在 time.sleep(3) 之后主动调用 self.screenshot() 获取画面。这样既避免了一轮操作中进行两次截图,又保证截图发生在路径连线稳定之后,与原有业务逻辑预期一致,没有引入新的风险。整体改动是合理的。

src/sr_od/app/sim_uni/operations/sim_uni_exit.py (3)

37-43: 入口节点改用 last_screenshot 的前提要确认生命周期

这里改为直接使用 self.last_screenshot,从逻辑上符合“每轮只截一次图”的优化方向,state 判定和后续分支也都仍然只依赖同一帧画面,没有新增逻辑风险。

需要确认两点:

  1. check_screen 作为 is_start_node=True 的节点,其所在轮次仍然会在进入前由框架刷新 last_screenshot
  2. 没有全局或配置层面对该节点关闭 screenshot_before_round,否则这里可能拿到 None 或上一次轮次的残留截图。

如果这两点在基类/装饰器层面已经保证,就没问题。


91-93: click_confirm 使用 last_screenshot 与重试机制配合良好

这里改用 self.last_screenshot 后,每次进入本节点依然只使用进入轮的那一帧图;遇到识别失败时,通过 retry_wait=1 和外层轮询机制重新进入节点时会获得新的截图,语义上与之前每次内部截屏等价。

整体看与周边节点保持了一致的用法,success_wait=6 也给了足够时间等待确认框消失,没有明显风险。


102-104: click_empty 的截图复用方式与其它节点保持一致

同样改为使用 self.last_screenshot,结合 success_wait=2, retry_wait=1 的配置,识别失败时会通过新一轮刷新截图再尝试点击,行为与原先按需截屏接近但更节约资源。

这块与 check_screen / click_confirm 的用法已经统一,便于后续维护。

src/sr_od/app/sim_uni/operations/bless/sim_uni_upgrade_bless.py (1)

45-45: 改用 last_screenshot 的前置条件需要确认

这几处把 screen = self.screenshot() 改为 screen = self.last_screenshot,逻辑上是复用框架已经截好的图,避免重复截图,思路是对的,代码也保持了原有调用方式(仍然通过局部变量 screen 传给后续逻辑)。

这里有两个前置条件建议你再确认一下:

  1. last_screenshot 的刷新时机

    • 对于所有相关节点(尤其是起始节点 check_leftis_start_node=True),需要保证在节点执行前,框架已经统一截图并更新了 self.last_screenshot
    • 如果某个节点的 operation_node 装饰器上显式设置了 screenshot_before_round=False,那它再依赖 last_screenshot 时要确保上一轮或上一个节点的截图仍然是“当前界面”,否则可能会识别到旧画面。
  2. 空值/异常场景

    • 如果在某些异常分支(比如上游节点提前 return 或中途异常恢复)里,last_screenshot 可能没有被赋值或被清空,调用处会直接把 None 传给后续 OpenCV / OCR 逻辑,容易抛错。
    • 可以在基类或公共入口集中兜底(保证每轮一定会先截图一次),这样这里就不需要额外判空。

整体上,这个修改方向没问题,只要上述前置条件在当前框架下已经满足,合并风险就很小。

Also applies to: 75-75, 123-123, 141-141, 167-167

src/sr_od/challenge_mission/choose_support_in_team.py (4)

34-48: 起始节点直接使用 last_screenshot,请确认生命周期保证

这里从 self.screenshot() 改为 self.last_screenshot 很符合「减少冗余截图」的目标,但 wait_at_firstis_start_node=True 的起始节点,完全依赖框架在本轮调用前已刷新好 last_screenshot。如果某些场景下该节点前没有统一截图,last_screenshot 可能为 None 或上一轮残留画面。

建议你确认/回顾:

  • @operation_node 的默认 screenshot_before_round 是否为 True
  • 如果在别处把本节点或上游节点的 screenshot_before_round 设为 False,是否还有其他路径更新 last_screenshot

如果上述前提都能保证,这里的改动就没问题。


50-60: 点击支援复用缓存截图实现合理

click_support 使用 self.last_screenshot 传入 round_by_find_and_click_area,避免在节点内部再次截图,逻辑与前一个节点保持一致,也符合减少截图次数的目标,没有看到功能性风险。

只要上一轮进入该节点前的 last_screenshot 已由框架刷新,这段实现可以保留现状。


90-99: 点击入队节点对缓存截图的使用与前序节点保持一致

click_join 同样改为使用 self.last_screenshot 传入 round_by_find_and_click_area,与 click_support 的模式统一,减少了潜在的重复截图,逻辑上没有引入新的分支或状态。

这段改动可以接受。


146-155: 尾节点使用 last_screenshot 检查队伍标题与整体方案一致

wait_at_last 使用缓存的 last_screenshot 检查是否回到带「队伍标题」的界面,和首节点、其他节点的写法统一,能避免在收尾阶段多余截图。

同样前提是框架在进入该节点前会更新 last_screenshot(或者明确你已经在其它地方关闭了自动截图并自行维护该字段),在此前提下这段实现没有明显问题。

src/sr_od/app/sim_uni/operations/move_v1/sim_uni_move_to_interact_by_detect.py (1)

71-71: Verify screenshot cache freshness guarantee

The change from self.screenshot() to self.last_screenshot reduces redundant screenshot operations, but requires confirmation that the operation framework automatically updates last_screenshot before each round invocation. Since the method's movement and interaction detection logic depends on accurate screen state (lines 74, 77, 108, 113), using a stale screenshot could cause incorrect movement decisions or missed interactions. Additionally, verify that the timing-sensitive logic at line 103 (now - self.start_move_time >= 2) remains accurate with cached screenshots rather than fresh captures.

src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_event_route_v2.py (3)

63-63: 优化合理,减少冗余截图。

此节点使用默认的 screenshot_before_round=True,框架会在执行前自动截图并更新 last_screenshot,因此使用缓存的截图是安全且高效的。


92-100: 优秀的优化:避免无效截图。

通过设置 screenshot_before_round=False 跳过自动截图,然后在 Line 99 调整视角后,在 Line 100 手动截取新的屏幕画面。这避免了在视角调整前截取无效的截图,是一个合理的性能优化。


44-44: Verify last_screenshot initialization before start node execution.

Using self.last_screenshot in the start node (line 44) depends on the framework guaranteeing that this attribute is initialized before the first node executes. Although the decorator has screenshot_before_round=True by default, manual verification is needed to confirm that the framework actually populates last_screenshot before executing the start node with this default behavior. If the framework does not initialize last_screenshot before the first node, this could result in an AttributeError at runtime.

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_wait_level_start.py (1)

36-69: 验证重试机制下的截图刷新策略。

check_screen 方法作为起始节点且配置了最多 20 次重试,现在使用 self.last_screenshot 进行屏幕状态检测。这可能存在问题:

  1. 如果重试时 last_screenshot 没有更新,所有 20 次重试都会基于同一张过时的截图进行状态判断
  2. 屏幕状态检测决定了后续操作分支(祝福选择、奇物选择等),使用过时截图可能导致错误的流程判断
  3. 作为 is_start_node=True 的起始节点,其截图来源的时效性尤为重要

建议确认操作框架在节点重试时是否会自动刷新 last_screenshot

此问题与 sim_uni_run_world.py 文件中的截图管理问题相关,可以通过同一个验证脚本进行检查。

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_world.py (1)

85-114: 验证截图缓存的时效性。

这三个方法 battle_fail_exitbattle_fail_exit_confirmclick_empty 现在都使用 self.last_screenshot 而不是实时截图。由于这些方法依赖准确的 UI 元素检测来完成战斗失败结算流程,需要确认:

  1. @operation_node 装饰器在每个节点执行前是否自动更新 last_screenshot
  2. 重试机制(retry_wait 参数)触发时是否会刷新截图
  3. 如果使用了过时的截图,是否会导致 round_by_find_and_click_area 无法找到目标区域
src/sr_od/sr_map/operations/choose_special_point.py (1)

37-37: 验证重试时的截图刷新机制

该方法从显式调用 self.screenshot() 改为使用缓存的 self.last_screenshot。由于该方法包含多个重试路径(第 58、79 行的 round_retry)且最大重试次数为 10,需要确认 @operation_node 装饰器在每次重试前是否会自动刷新 last_screenshot

如果框架在重试时不刷新截图,则:

  • 方法执行拖动操作后(第 57、76 行),仍会使用旧截图进行后续判断
  • 无法检测到 UI 状态变化,可能导致重试逻辑失效
  • 可能陷入使用相同陈旧截图重试 10 次的循环

请验证 operation_node 装饰器在重试时的截图捕获行为,以确认此更改是否安全。

src/sr_od/app/div_uni/operations/ornamenet_extraction.py (4)

182-196: 使用缓存截图可接受,但需确保时效性。

此方法根据战斗状态决定点击"退出关卡"还是"再来一次"按钮。使用 self.last_screenshot 是可接受的,前提是操作框架在此节点执行前已捕获最新截图。


126-134: Verify screenshot behavior in operation framework before approving this change.

The original review comment asserts that the operation framework automatically captures screenshots before each round (via screenshot_before_round default), making the switch from self.screenshot() to self.last_screenshot safe. However, this assumption cannot be verified without access to the operation framework's implementation.

This is particularly critical for methods with retry logic (wait_mission_loaded and wait_back with node_max_retry_times=20). If self.last_screenshot is not refreshed between retry attempts, the retry mechanism becomes ineffective for detecting state changes.

Before approving these changes, confirm:

  1. The default value of screenshot_before_round in the @operation_node decorator
  2. Whether screenshots are captured before each retry attempt, not just before the initial round
  3. For non-retry methods, whether the cache is guaranteed to be fresh from the current round

136-144: 需要验证重试操作的截图刷新机制。

此方法是一个等待/轮询操作,配置了 node_max_retry_times=20。使用 self.last_screenshot 存在潜在风险:如果在重试之间不刷新截图,所有 20 次重试都会检查同一张陈旧的图像,导致轮询机制失效。

确认以下内容:

  • operation_node 装饰器的重试机制是否在每次重试前自动刷新截图
  • self.last_screenshot 属性在重试循环中的更新时机
  • round_by_find_area 方法是否会自动捕获新的截图

198-206: 重试操作中使用缓存截图需要验证框架行为。

此方法配置了 node_max_retry_times=20,在重试场景下,需要确认 self.last_screenshot 是否在每次重试前自动更新。若截图未刷新,所有重试都会检查相同的陈旧图像,导致无法正确检测角色是否已返回大世界。请确认操作框架在重试循环中是否自动刷新截图。

src/sr_od/app/sim_uni/operations/entry/sim_uni_claim_weekly_reward.py (1)

26-26: LGTM - 截图来源修改一致

三个方法(_wait_ui_check_reward_claim_reward)都统一使用 self.last_screenshot 作为截图来源,与整个 PR 的优化模式保持一致。这些方法通过 @node_from 形成操作链,假设装饰器会在每个节点执行前更新截图,这个修改是合理的。

Also applies to: 38-38, 45-45

src/sr_od/operations/click_dialog_confirm.py (1)

24-26: LGTM - 简单操作的截图优化

将对话框确认操作的截图来源改为 self.last_screenshot 符合 PR 的优化目标,逻辑保持不变。

src/sr_od/app/nameless_honor/nameless_honor_app.py (1)

105-110: LGTM - 奖励领取后的画面检查

_check_screen_after_reward 方法使用 self.last_screenshot 来检查领取奖励后的画面状态,与 PR 的优化模式一致。该方法用于检测是否仍在无名勋礼界面或需要关闭弹窗。

src/sr_od/interastral_peace_guide/guide_choose_category.py (1)

29-31: LGTM - 指南分类选择的截图优化

wait_screenchoose 两个方法都改用 self.last_screenshot,符合整体优化模式。这些方法用于画面等待和分类选择,使用缓存截图可以减少截图开销。

Also applies to: 39-41

src/sr_od/app/sim_uni/operations/battle/sim_uni_fight_elite.py (1)

32-39: LGTM - 战斗检测的截图优化

_check_enemy_fight 方法改用 self.last_screenshot 进行敌人检测和战斗状态判断。这些是关键的战斗流程方法:

  • _check_enemy 使用 OCR 和 YOLO 检测敌人
  • _fight 判断是否进入战斗画面

修改与 PR 模式一致,假设装饰器保证截图时效性。

Also applies to: 59-61

src/sr_od/app/echo_of_war/challenge_ehco_of_war.py (2)

65-70: LGTM - 挑战流程中的截图优化

四个方法(after_click_challengeafter_click_start_challenge_after_battle_resultafter_challenge_again)改用 self.last_screenshot 来检查对话框和战斗结果。这些方法在点击操作后检查画面状态,使用缓存截图是合理的。

Also applies to: 137-139, 172-177, 186-192


149-155: 注意:保留了新截图的使用

_wait_battle_result 方法(第155行)仍然使用 self.screenshot() 获取新截图,而其他方法使用 self.last_screenshot。这个差异应该是有意为之的,因为该方法需要主动等待战斗结果状态变化,必须获取最新的屏幕状态。

src/sr_od/app/sim_uni/operations/event/sim_uni_reward.py (1)

42-44: LGTM - 沉浸奖励获取流程的截图优化

三个方法(_get_reward_click_empty_check_state)统一改用 self.last_screenshot,形成完整的奖励领取节点链。这些方法分别负责:

  • 获取奖励并进行 OCR 识别数字
  • 点击空白处关闭
  • 检查画面状态

使用缓存截图符合 PR 的优化目标,逻辑保持不变。

Also applies to: 88-94, 104-110

src/sr_od/operations/back_to_normal_world_plus.py (1)

26-28: Verify screenshot freshness in check_screen method

The change from self.screenshot() to self.last_screenshot affects a critical screen state detection method. Confirm that the @operation_node decorator automatically captures or updates self.last_screenshot before this method executes, ensuring the screenshot is current and not stale from a previous operation. This is important because with node_max_retry_times=20, using an outdated screenshot could cause incorrect state detection and waste retry attempts.

src/sr_od/app/sim_uni/operations/auto_run/sim_uni_run_level.py (2)

111-118: 改用 last_screenshot 避免重复截图,逻辑保持不变

这里直接使用 self.last_screenshot 作为楼层类型识别输入,在假定 operation_node 默认会在节点开始前刷新截图的前提下,与原先在节点内部调用 self.screenshot() 的语义一致,但少一次截图开销。后续如果对该节点调整 screenshot_before_round 配置,记得同时确认 last_screenshot 的更新来源即可。


133-149: 路线匹配同样复用 last_screenshot,提升一致性与性能

_check_route 复用当前轮的 self.last_screenshot 做小地图裁剪和路线匹配,避免额外截屏,同时确保同一帧图像在整个匹配过程内被一致地使用,有利于结果稳定。只需在后续改动中注意不要关闭该节点前置截图而忘记其它截图来源。

src/sr_od/app/assignments/assignments_app.py (4)

54-57: 选择专属材料改用 last_screenshot,OCR 行为保持一致

这里将 screen 改为读取 self.last_screenshot 并传给 round_by_ocr_and_click,在节点开始前截图已刷新的前提下,等价于原先基于最新画面做 OCR,只是避免在节点内部重复截屏,符合本 PR 的目标。


62-68: 一键领取在查找与点击间复用同一帧截图,更稳定也更省开销

_claim_all 先用 screen = self.last_screenshot 调用 round_by_find_area,再在成功时用同一个 screen 调用 round_by_find_and_click_area,确保“发现按钮”和“点击按钮”基于同一帧图像,减少因界面轻微变化导致状态不一致的可能,同时避免额外截屏。


73-75: 再次派遣改为显式传入 last_screenshot,一致性良好

assign_again 直接将 self.last_screenshot 传入 round_by_find_and_click_area,与同文件其它节点用法保持一致,也避免在 helper 内部再触发截图逻辑,符合减少冗余截图的目标。


80-87: 领取节点同样复用 last_screenshot,语义清晰

_claim 中查找“委托-领取”并点击都基于 self.last_screenshot,逻辑与使用最新屏幕判定按钮是否存在/点击的需求一致,同时避免多次截屏;成功/失败分支返回的状态也未改变。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/sr_od/challenge_mission/choose_challenge_times.py (1)

27-44: 使用 last_screenshot 的重试语义需要再确认一下

choose() 这里改为直接复用 self.last_screenshot,对单轮执行来说可以避免多次截图,而且同一轮内 _get_current_times/_click_plus/_click_minus 共用同一张图,逻辑上是更一致的。
但结合 node_max_retry_times=5,有两个点建议你确认:

  • 重试时 @operation_node 是否保证在每一轮开始前都会重新截图并更新 last_screenshot?如果没有,每次重试都用同一张旧图,可能导致一直无法感知界面已变化。
  • 是否存在在同一轮内执行其他节点/操作改变 UI,再回到 choose() 的情况?这种情况下也需要保证 last_screenshot 在回到本节点前被刷新。

另外,原来的 screen: MatLike = ... 类型注解现在去掉了问题不大,但如果你希望继续享受静态检查,可以考虑在类属性里给 last_screenshot 标注类型,或在这里保留一个显式注解(非必须)。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddac058 and 6074998.

📒 Files selected for processing (16)
  • src/sr_od/app/assignments/assignments_app.py (4 hunks)
  • src/sr_od/app/claim_email/email_app.py (2 hunks)
  • src/sr_od/app/nameless_honor/nameless_honor_app.py (6 hunks)
  • src/sr_od/app/sim_uni/operations/entry/choose_sim_uni_diff.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/entry/choose_sim_uni_num.py (1 hunks)
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_start.py (3 hunks)
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (3 hunks)
  • src/sr_od/app/support_character/support_character_app.py (2 hunks)
  • src/sr_od/challenge_mission/choose_challenge_times.py (1 hunks)
  • src/sr_od/challenge_mission/click_challenge.py (1 hunks)
  • src/sr_od/challenge_mission/click_start_challenge.py (1 hunks)
  • src/sr_od/interastral_peace_guide/guid_choose_tab.py (1 hunks)
  • src/sr_od/interastral_peace_guide/guide_choose_mission.py (2 hunks)
  • src/sr_od/operations/interact/talk_interact.py (1 hunks)
  • src/sr_od/operations/menu/click_phone_menu_item.py (1 hunks)
  • src/sr_od/operations/menu/open_phone_menu.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/sr_od/app/nameless_honor/nameless_honor_app.py
  • src/sr_od/app/assignments/assignments_app.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-13T12:07:30.874Z
Learnt from: JoshCai233
Repo: OneDragon-Anything/StarRailOneDragon PR: 521
File: src/sr_od/app/sim_uni/sim_uni_app.py:61-87
Timestamp: 2025-10-13T12:07:30.874Z
Learning: 在 `_check_points_reward()` 中,`OperationRoundResult` 的语义与常规不同:`FAIL` 表示"奖励未完成,需要继续执行自动化",而 `SUCCESS` 和 `RETRY` 表示"提前返回,跳过自动化"。这是因为调用方使用 `if result != FAIL: return` 来判断是否跳过后续的自动化脚本执行。

Applied to files:

  • src/sr_od/challenge_mission/click_challenge.py
  • src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py
  • src/sr_od/app/sim_uni/operations/entry/sim_uni_start.py
  • src/sr_od/challenge_mission/click_start_challenge.py
  • src/sr_od/app/sim_uni/operations/entry/choose_sim_uni_diff.py
  • src/sr_od/app/claim_email/email_app.py
🧬 Code graph analysis (1)
src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (1)
src/one_dragon/base/operation/operation_node.py (1)
  • operation_node (72-100)
🔇 Additional comments (20)
src/sr_od/app/sim_uni/operations/entry/choose_sim_uni_num.py (1)

41-69: 复用 last_screenshot 的前置条件需要确认

这里改为直接使用 self.last_screenshot,和本次 PR “减少重复截图、统一复用缓存屏幕”的思路一致,没有看到明显逻辑问题。

有一个小点建议确认一下:

  • 目前 choose() 一进来就把 screen 传给 common_screen_state.in_secondary_ui(...),而不像 _get_current_num / _is_going 那样在 screen is None 时兜底再截图。
  • 也就是说,这段代码假设在每个 round 开始之前,框架层已经保证 self.last_screenshot 是最新且非空。

如果框架的 @operation_node 默认确实会在 round 开始前统一截图并写入 last_screenshot,那这次改动是安全的;否则建议要么在这里对 screen is None 时兜底调用一次 self.screenshot(),要么确保 in_secondary_ui 能安全处理 screen=None 的情况。

整体上改动方向是对的,只是需要确认一下这一点以避免偶发的 None 导致识别失败。

src/sr_od/challenge_mission/click_start_challenge.py (1)

25-29: 使用 last_screenshot 替代即时截图的语义确认

这里改为使用 self.last_screenshot,在默认 operation_node 会在轮次开始前自动截图并更新缓存的前提下,可以避免重复截图,逻辑上是等价且更高效的。需要你确认一下:在该节点执行前框架是否一定会更新 last_screenshot(特别是作为 is_start_node=True 的节点时),以避免极端情况下拿到上一轮遗留的旧截图导致识别/点击失败。

src/sr_od/challenge_mission/click_challenge.py (1)

24-27: 确认 last_screenshot 的刷新时机以避免使用过期截图

这里从即时 self.screenshot() 改为复用 self.last_screenshot,在默认的 @operation_node 行为(每轮开始自动截图并更新 last_screenshot)下应该是等价且更省性能的。
建议你再确认两点:

  • 确认此节点所在流程中,本轮内不会在别处发生 UI 变化后才调用 click(),否则有可能点的是上一帧状态。
  • 确认没有在装饰器或基类层面把该节点的 screenshot_before_round 关掉(比如通过全局配置覆盖),避免连续重试时一直用同一张旧截图。

如果以上前提都满足,这个改动是安全且合理的。

src/sr_od/interastral_peace_guide/guid_choose_tab.py (1)

28-28: Verify that last_screenshot remains fresh across operation rounds.

The change from self.screenshot() to self.last_screenshot improves performance by reusing the cached screenshot. However, this approach depends on the framework guarantee that last_screenshot is refreshed before each operation round executes. Since the @operation_node decorator does not explicitly set screenshot_before_round=False, confirm that:

  1. The decorator automatically captures a fresh screenshot before invoking this method
  2. The cached last_screenshot is updated at the appropriate time to ensure UI state checks on lines 30, 33, and 36 use current screen data

If these guarantees are not provided by the framework, the UI detection logic may operate on stale screen state.

src/sr_od/app/claim_email/email_app.py (2)

45-51: 改用 last_screenshot 作为菜单识别输入整体是合理的

这里直接复用 self.last_screenshot 来做菜单项匹配,可以去掉多余的显式截图调用,逻辑上与“本轮开始前自动截图”是等价的,只要框架在进入节点前已经刷新了截图。
建议你再确认两点:

  1. @operation_node 的默认参数是否为 screenshot_before_round=True,确保进入 _click_email 前一定会刷新 last_screenshot
  2. 在极端情况下(例如异常恢复后)last_screenshot 不会为 None,否则传入匹配函数可能会触发异常。

56-58: _claim 中使用缓存截图也依赖节点前自动刷新,注意实际场景验证

_claim 同样改为使用 self.last_screenshot,考虑到上一个节点 _click_email 在成功时返回 wait=1,再配合 @operation_node 的默认截图行为,这里拿到的应当是“进入收件箱界面后”的最新画面,从而避免多余截图,设计上是统一且合理的。
为了保险,建议在实际游戏中至少跑一遍不同语言 / 分辨率场景,确认“邮件-全部领取”在第一次进入该节点时识别和点击依旧稳定,没有出现因为截图时机变化导致的漏识或误点。

src/sr_od/app/sim_uni/operations/entry/choose_sim_uni_diff.py (1)

38-52: Verify screenshot freshness for start nodes using last_screenshot

This method is marked as is_start_node=True, meaning it executes at the start of an operation flow. Using self.last_screenshot carries timing risks:

  1. If the operation_node framework does not auto-capture screenshots before executing start nodes, last_screenshot may be stale from a previous operation
  2. Stale screenshots would cause inaccurate UI state validation at lines 44-46, potentially causing incorrect behavior
  3. The retry mechanism (node_max_retry_times=5) cannot compensate if the screenshot remains outdated

Verify the framework's behavior:

  • Does operation_node auto-capture before each node execution?
  • For is_start_node=True nodes specifically, is last_screenshot guaranteed fresh?
  • Does the implementation add screenshot_before_round=False parameter, or is explicit self.screenshot() needed?
src/sr_od/app/sim_uni/operations/entry/sim_uni_start.py (3)

45-54: 改动符合 PR 目标。

作为后续节点,使用 self.last_screenshot 替代手动截图是合理的,减少了冗余的截图操作。逻辑流程保持不变。


56-69: 改动符合 PR 目标。

使用 self.last_screenshot 替代手动截图,与整个 PR 的优化方向一致。方法逻辑未改变,仍正确处理低等级确认流程。


28-43: Verify screenshot initialization for start node operation.

This entry point is marked as is_start_node=True and now depends on self.last_screenshot for screen state detection. Confirm that the operation framework automatically captures a screenshot before invoking start nodes to ensure last_screenshot is properly initialized and current at this execution point.

This verification requires inspection of:

  • The @operation_node decorator implementation to confirm automatic screenshot capture behavior
  • The SrOperation base class initialization to verify last_screenshot is set before the first node executes
  • Whether screenshot_before_round parameter or similar mechanism controls screenshot timing
src/sr_od/app/sim_uni/operations/move_v2/sim_uni_run_combat_route_v2.py (3)

51-51: 优化得当:复用缓存截图

此处改用 self.last_screenshot 是正确的。由于该节点使用了默认的 screenshot_before_round=True,框架会在执行方法前自动截图并存储到 self.last_screenshot,直接复用可避免重复截图开销。


76-76: 优化得当:复用缓存截图

此处改用 self.last_screenshot 是正确的。尽管该节点有多个入口,但由于使用了默认的 screenshot_before_round=True,框架会在每次调用前自动更新截图,因此复用缓存截图是安全且高效的。


145-154: 优化得当:精确控制截图时机

此处设置 screenshot_before_round=False 是正确的优化。由于该方法需要先调用 _view_down() 调整视角(第 152 行),然后在调整后截图(第 154 行),设置该参数可避免在视角调整前拍摄无用的截图,精准地消除了冗余截图开销。

src/sr_od/interastral_peace_guide/guide_choose_mission.py (2)

31-38: Verify that last_screenshot is properly initialized before the start node executes.

As a start node (is_start_node=True), the wait_screen method now relies on self.last_screenshot instead of calling self.screenshot() directly. Confirm that the framework guarantees the screenshot has been updated before this node executes, otherwise the state check may fail if last_screenshot is uninitialized or stale.


40-55: Verify screenshot update timing in the retry mechanism.

This method performs a drag operation (line 50) to change the interface state when the transport button is not found, then retries with a 2-second wait (line 52). If self.last_screenshot is not updated after the drag operation, the retry will use the old screenshot from before the drag, preventing detection of newly appeared transport buttons.

Confirm whether the framework automatically captures a fresh screenshot at the start of each retry round. If not, explicitly call self.screenshot() after the drag operation or before returning from round_retry() to ensure the next iteration sees the updated interface state.

src/sr_od/operations/menu/click_phone_menu_item.py (1)

29-38: 滚动后使用缓存截图可能导致检测失败。

在滚动菜单后(line 37),如果框架不自动更新 last_screenshot,第38行的 round_retry 会继续使用滚动前的旧截图进行下一轮检测,导致:

  1. 无法检测到滚动后新出现的菜单项
  2. 持续重试相同的过期画面数据,直到达到最大重试次数(10次)
  3. 操作失败率增加

这个场景比单纯的状态检查更关键,因为涉及用户交互(滚动)改变了UI状态。

请运行相同的验证脚本(见 open_phone_menu.py 的评论),特别关注在有UI交互操作(如滚动、点击)后,框架是否保证在下一轮重试前更新截图缓存。

src/sr_od/operations/interact/talk_interact.py (1)

37-49: OCR operations should use fresh screenshot data in dynamic dialog scenarios.

When OCR is performed after UI interactions (click operations at lines 48+), ensure the screenshot data reflects the current dialog state. Consider verifying that self.last_screenshot is refreshed before the OCR operation at line 43, especially after click actions that modify the dialog content.

Additionally, restore the MatLike type annotation on line 39 for improved code clarity and maintainability.

src/sr_od/operations/menu/open_phone_menu.py (1)

23-25: 验证截图缓存的更新时机。

将截图源从 self.screenshot() 改为 self.last_screenshot 可以减少冗余截图,但需要确保操作框架在每次节点执行(包括重试)前自动更新 last_screenshot。如果使用过期的截图数据,可能导致:

  1. 无法正确识别菜单是否已打开
  2. 重试时使用相同的过期截图,导致重试逻辑失效
  3. is_normal_in_world 状态判断错误

需要验证 operation_node 装饰器的实现,确认:

  • last_screenshot 是否在每轮执行前自动更新
  • screenshot_before_round 参数的默认行为
  • node_max_retry_times=10 时重试的截图更新机制
src/sr_od/app/support_character/support_character_app.py (2)

49-55: 验证前序节点点击后截图是否已更新

前一个节点 _click_ellipsis 在第 43 行执行点击并等待 1 秒后,当前节点使用 self.last_screenshot 进行匹配。若 @operation_node 装饰器不会在节点执行前自动刷新屏幕截图,第 50 行的匹配逻辑将基于过时的屏幕数据,导致匹配失败或定位错误。

请确认 @operation_node 装饰器的实现是否会在每个节点开始前自动获取新的屏幕截图。


38-44: Verify @operation_node decorator behavior and last_screenshot update timing.

The optimization to use self.last_screenshot instead of self.screenshot() appears reasonable, but requires confirmation that the @operation_node decorator automatically updates self.last_screenshot before each node executes. Without this guarantee, line 38 may reference stale screen data from before the previous operation.

Ensure that:

  • @operation_node captures a fresh screenshot before the decorated method executes
  • self.last_screenshot is populated and current when referenced at line 38
  • The screenshot remains valid through consecutive operation nodes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant