Skip to content

Commit b96c48c

Browse files
yiyiyiyiGKYKrabbypattylHongzhi Wen
authored
feat(cat-idle): add three-tier return cat idle flow (#1581)
* feat(idle): add auto-goodbye controller with task guard Phase 1:新增首页自动 goodbye 控制器 - 新增 app-auto-goodbye.js,实现首页闲置检测与自动 goodbye - 控制器只在首页启动,不注入 /chat;启动前等待 storage location barrier - 到点后仅派发现有 live2d-goodbye-click,不复制 goodbye 业务逻辑 - 阻断条件收紧为三类:真实对话 / 持续任务(queued+running)/ 真实执行操作 - 教程接管和 yui-taking-over 期间不触发 - app-interpage.js 补充 /chat 真实交互回传首页,刷新闲置基线 - 开发阈值暂为 5s / 10s / 15s,最终验收后恢复 20min / 30min / 40min - 补充 Phase 1 harness 测试 * feat(idle): replace return-ball visual with CAT1/CAT2/CAT3 tiers Phase 2+3:return-ball 显示层替换为 CAT1/CAT2/CAT3 占位表现 - avatar-ui-buttons.js 新增 visualTier 桥接,监听 neko:auto-goodbye:state-change 切换 cat-idle-cat1/2/3.png,保留原有 DOM ID、事件、拖拽协议 - CSS 为 CAT1/CAT2/CAT3 提供逐级更小的尺寸和更淡的阴影 - 占位资源暂为白底文字图,正式资源后续替换 - 补充 Phase 2/3 静态契约测试,锁住视觉已接入、旧 rest_off/rest_on 已退出 - Phase 3 harness 断言 5s→cat1→10s→cat2→15s→cat3,不重复派发 goodbye * feat(idle): dock minimized chat ball next to CAT2/CAT3 return-ball Phase 4:聊天球与 CAT2/CAT3 表现层停靠 - idle-dock 作为完全独立的编排模块,不修改 setMinimized() 函数体 - setMinimized 保持原始签名,内部零 idle-dock 分支 - CAT2/CAT3 激活时:如已最小化直接停靠,否则先调用原始 setMinimized(true) 再通过 MutationObserver 等动画完成后停靠 - return 时恢复到停靠前保存的位置 - 用户直接展开停靠球时,toggleMinimized 在展开前恢复位置,避免错位 - /chat 独立窗口通过 isElectronChatWindow() 全程排除 - 不新增对外公开桥接 - CSS .is-idle-docked 提供停靠态轻量 glow 和位置过渡 - 补充 Phase 4 静态契约测试 * test(idle): add regression boundary tests and asset version tracking Phase 5:回归与边界验证 - 新增 test_phase5_regression_boundary.py(10 条断言)锁住核心约束: 手动 goodbye 未改、auto-goodbye 只派发事件、return 走 handleReturnClick、 start_session 未误触、queued/running 阻断、deferred reminder 无豁免、 旧 chat-container 不引用、教程/拖拽 guard 存在 - 新增 test_auto_goodbye_goodbye_return_contract.py 锁住 goodbye/return 语义: auto-goodbye 复用现有 goodbye 链路,return 不走 start_session - pages_router.py 补充所有新增资源的版本跟踪 - .gitignore 排除 .agent/notes/ * fix(idle): prevent duplicate goodbye dispatch and idle-dock expand fight Code review 修复两个实际 bug: 1. tryAutoGoodbye() 补 autoGoodbyeTriggered 前置守卫: 如果 goodbye manager 没设上 _goodbyeClicked, 旧代码每 500ms tick 会重复派发 live2d-goodbye-click。 现在在派发前检查 autoGoodbyeTriggered 阻止重复。 2. toggleMinimized() 补 idleDockTier 清理: 用户直接展开停靠球时,旧代码只清 idleDockActive, 没清 idleDockTier。展开动画期间如果 tier 事件到达, isIdleDockTierActive() 仍为 true,会重新进入 idle-dock 和展开动画打架。现在同步清理 tier 和所有 observer。 * feat(idle): replace placeholders with GIF-based tier images and strip all CSS effects 将占位图片替换为基于 sleep_cat_wait3.gif 的新图片,并为各层级添加中文描述文字: - CAT1 非点击态"摸摸我~",点击态"我在这!" - CAT2 非点击态"好无聊...",点击态"还在吗?" - CAT3 非点击态原始无文字 GIF,点击态"别吵..." - 删除旧的 SVG 占位文件和 cat-idle-cat3.png 彻底清除所有按钮视觉效果: - CSS 移除所有 border、background、box-shadow、backdrop-filter、::before 伪元素 - 移除 vrm-ui-buttons.js 独立注入的呼吸灯动画 - 移除 common-ui-hud.js 中 #live2d-btn-return 呼吸灯样式 - 悬停动效改为图片切换(非点击态↔点击态),不再使用 CSS 变换 - 删除所有 CAT3 特判代码 * revert: remove .agent/ from .gitignore (undo from 251b48a) 取消 251b48a 中对 .gitignore 的修改,不再排除 .agent 目录。 * fix(idle): normalize cat return button size 统一 CAT 空闲回来入口的展示尺寸,移除按 cat2/cat3 分档缩放的覆盖规则。\n\n避免不同空闲档位切换时按钮尺寸跳变,并保持移动端尺寸与当前设计口径一致。 * feat(ui): use gif idle return cat assets * fix(ui): smooth cat idle state transitions 统一猫 idle 资源版本跟踪与测试口径为 GIF。\n\n为 CAT1/CAT2/CAT3 阶段切换增加交叉淡入淡出、轻微位移缩放和 reduced-motion 兼容,避免直接换图造成生硬跳变。\n\n补充 return 猫球首次出现的淡入上浮效果,并用静态测试锁定平滑切换契约。\n\n验证:node --check static/avatar-ui-buttons.js && node --check static/app-ui.js;uv run python -m pytest tests/unit/test_avatar_return_button_cat1_static.py tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_react_chat_idle_dock_static.py -q。 * fix(ui): preserve cat idle interactions 修复 CAT2/CAT3 下拖拽和松手会刷新 idle 基线、导致状态退回 CAT1 的问题。\n\n优化 return 猫形象 hover 反馈:根据对应 click GIF 的帧延迟解析一轮播放时长,鼠标离开后等播放完成再恢复默认态;同一 GIF 反复进入不重复设置 src,避免一直从头播放。\n\n同步更新 cat idle 设计文档,记录当前 goodbye 后交互不重置 tier、hover GIF 播放策略和相关测试覆盖。\n\n验证:node --check static/avatar-ui-buttons.js;node --check static/app-auto-goodbye.js;uv run python -m pytest tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_avatar_return_button_cat1_static.py tests/unit/test_react_chat_idle_dock_static.py -q。 * feat: bridge cat idle states to desktop chat 更新猫娘空闲态链路:goodbye 后 CAT1/CAT2/CAT3 可在 suppression 存在时继续推进。\n\n补齐 return-ball 桌面状态广播、Electron 聊天窗折叠停靠、拖拽跟随和退出竞态保护。\n\n同步更新设计文档与回归测试,明确 GIF 资源、桌面桥接和边界约束。 * feat: 优化猫猫 idle 阶段子动作 - 增加 CAT1 走向聊天框、停下伸懒腰和 hover 子动作资源 - 同步桌面 return-ball 拖拽坐标与聊天窗跟随逻辑 - 将联调 idle 阈值调整为 20s/25s/30s,并更新测试与设计文档 * fix: bridge desktop chat target for cat idle walk - 让 Electron /chat 折叠态发布聊天小球 screen rect - 通过 app-interpage 转发给 pet 页,CAT1 寻路时换算为当前窗口坐标 - 补充静态合同测试和设计文档,避免桌面端只停留在默认 CAT1 * fix: keep desktop cat walk target fresh during hover - 为 Electron 聊天小球 minimized screen rect 增加低频心跳刷新 - 避免 CAT1 走路暂停播放点击态后,因目标 rect 过期回到默认猫 - 补充 idle-dock 静态合同测试 * fix: preserve cat art on desktop drag start - 桌面 return-ball drag start 只取消 CAT1 自动移动,不再重置当前 GIF - 保留 observer 和当前视觉,避免按下瞬间闪回 CAT1 默认猫 - 补充 CAT1 拖拽合同测试 * feat(idle): add drag action gifs for return cat 为 CAT1/CAT2/CAT3 return 猫接入独立拖拽临时态,拖拽越过阈值后切换到对应 move GIF,松手后恢复当前真实 tier。\n\n补齐普通网页端 return-ball 拖拽初始化,桌面多窗口仍沿用 app-ui 拖拽桥接,避免双重监听;同时加入可开关诊断日志、静态资源版本跟踪、设计文档和契约测试。\n\n验证:node --check static/avatar-ui-buttons.js;node --check static/app-ui.js;uv run python -m pytest tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_react_chat_idle_dock_static.py tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_phase5_regression_boundary.py -q;Playwright 最小 DOM 模拟拖拽切换与恢复。 * feat(idle): vary cat walk timing and gif speed 为 CAT1 走向最小化聊天框增加随机起步等待:大多数情况下立刻或短等待,少量等待几十秒到几分钟;已经在走路时更新目标不重新等待。\n\n走路中如果猫与聊天球距离被拉大,移动倍率最高提升到 1.5x,并通过运行时 patch GIF 帧 delay 生成加速 Blob URL,让走路 GIF 本身随倍率加速。\n\n同步更新猫 idle 设计文档和静态契约测试。\n\n验证:node --check static/avatar-ui-buttons.js;uv run python -m pytest tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_react_chat_idle_dock_static.py tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_phase5_regression_boundary.py -q;Playwright 最小 DOM 模拟长等待、即时起步、距离拉大后的 1.5x Blob 加速。 * fix(cat-idle): resume interrupted walk after drag 修复 CAT1 猫在走向最小化聊天框时被拖拽打断后,松手可能停回默认态的问题。 记录拖拽前是否处于 walking 子状态,只在该场景下跳过重新抽取 start delay;真正重新开始走路仍保留原有随机等待设计,并补充静态契约测试。 * fix(chat): stabilize desktop idle cat dragging 修复桌面端三态猫拖拽视口恢复时的闪烁问题:在恢复窗口前先提交隐藏帧,避免拖拽态 GIF 出现在左上角。\n\n同时保留 128px 拖拽视口以避免 GIF 被裁切,并合并 return-ball 拖拽广播、缓存 Electron idle-dock bounds/workArea,减少最小化聊天球跟随卡顿。\n\n补充静态回归,覆盖桌面拖拽视口尺寸、隐藏帧刷新顺序和 Electron idle-dock 缓存契约。 * fix(chat): end native cat drag synchronously 修复上一版在 return-ball 松手时等待 rAF 才调用 nekoPetDrag.stop 的生命周期错误。\n\n现在松手后只同步隐藏并强制样式落地,然后立即停止主进程拖拽,避免桌面窗口停留在 128px 小窗并继续黏在鼠标上。\n\n更新静态测试,锁定 stop 不能被动画帧等待阻塞。 * fix(cat-idle): align desktop return cat drag lifecycle 按三态猫设计收口拖拽生命周期:手动拖拽取消当前 CAT1 自动走路,松手后恢复真实 tier 并走正常距离重判与随机 start-delay,不再通过 resumeWalkAfterDrag 跳过等待。\n\n真正开始 walking 时同步推进第一步,避免 walking GIF 在首帧原地播放;同时将桌面 return-ball 拖拽视口调整为 160px,给切换图和拖拽图留出渲染余量。\n\n验证:node --check static/avatar-ui-buttons.js;node --check static/app-ui.js;node --check static/app-react-chat-window.js;uv run python -m pytest tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_react_chat_idle_dock_static.py tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_phase5_regression_boundary.py -q;git diff --check。 * fix(cat-idle): stabilize return ball drag lifecycle 修复三态猫桌面 return-ball 拖拽收尾:小位移不再触发 CAT1 重新寻路,拖拽态在 renderer 完成 viewport 和 DOM 清理后再恢复显示。\n\n同时收口非桌面 return 按钮重复拖拽绑定,补齐 VRM 拖拽生命周期事件,并提交更新后的拖拽态 GIF 资产。 * fix(cat): stabilize idle drag demotion 按三态猫设计更新拖拽回退规则:CAT3 前两次拖拽保持,第三次回退到 CAT2,CAT2 拖拽回退到 CAT1,CAT1 保持原时间推进。补充桌面端 idle-dock 拖拽降级后的保位退出逻辑,避免 viewport-resize 抢先结束生命周期,并在 collapsed bounds 提交 busy 时进行受 generation 保护的短重试和兜底。同步功能/参考文档、后续交互扩展文档和相关回归测试。 * fix(cat): stabilize idle cat pair movement 修复 CAT1 小移动在真实点击返回后被遗留 hover 状态卡住的问题。\n\n同步三态自然推进发布阈值为 10min / 15min / 18min,并将 CAT1 小移动随机间隔调整为 5s-5min。\n\n更新猫 idle 三态设计文档与静态契约测试,明确 pair-move 生命周期、hover 收尾和当前时间参数。 * docs(cat): consolidate idle state design docs 清理 cat idle feature 文档中的文档连接。 删除已不再保留的 interaction expansion 和 reference 设计文档,让远端只保留当前收敛后的功能说明入口。 * fix(cat): harden idle cat desktop sync 完善三态猫桌面端 CAT1 小移动同步,补齐 BroadcastChannel 内的 pair-move bounds 转发与 Electron 折叠聊天窗消费,并防止 CAT1 同步覆盖 CAT2/CAT3 idle-dock。\n\n修复 auto-goodbye socket 断开后的 primed 状态、pending idle-dock 退出清理、return-ball viewport 等待兜底、VRM 拖拽结束判定顺序和 CAT1 pair-move 定位锚点问题。\n\n补充静态回归测试,锁定脚本加载顺序、资产版本追踪、拖拽生命周期和跨窗口同步边界。 * fix(cat): align drag demotion timing 修正 CAT2 拖拽降级到 CAT1 后的视觉阶段计时,从 CAT1 阈值开始重新等待完整 CAT1 显示窗口,避免错误保持 15 分钟。\n\n收紧 return button CSS 静态测试 helper,要求精确匹配顶层 .neko-idle-return-btn 基础规则,并在未找到规则时显式失败。\n\n验证:pytest tests/unit/test_app_auto_goodbye_phase1.py tests/unit/test_auto_goodbye_goodbye_return_contract.py tests/unit/test_react_chat_idle_dock_static.py;pytest tests/unit/test_avatar_return_button_idle_tiers_static.py tests/unit/test_app_auto_goodbye_phase1.py;node --check static/app-auto-goodbye.js。 * fix(cat): align idle cat dock movement - 支持 CAT1 settled 后在聊天框展开时只移动猫、聊天框最小化时猫和小球一起随机小移动。 - 收口桌面 compact/full 进入 CAT2/CAT3 的折叠、恢复和拖拽降级保留位置链路。 - 更新三态猫设计文档与静态回归测试,锁住展开/最小化/桌面 compact 边界。 * test(cat): clarify idle dock contract failures - 为 idle-dock 静态测试的 _between 辅助函数补充明确的分隔符缺失错误。 - 在比较 renderWindow 与 setMinimized 顺序前先校验 token 存在,避免失败时只抛难读的 ValueError。 * feat(neko-idle): add cat idle voice cues 为 CAT1/CAT2/CAT3 空闲猫形态接入轻量音效。 CAT1 非拖拽状态按三分钟窗口随机播放轻声音效,CAT1 拖拽态播放专属音效并在拖拽结束后淡出。CAT2/CAT3 睡觉形态按五分钟窗口随机播放对应睡觉音效,所有音量均保持较轻。 同步静态资源版本跟踪和三态猫静态契约测试。 * Fix idle dock static restore assertion * Allow auto goodbye on character routes * Avoid duplicate Live2D return drag wiring --------- Co-authored-by: Krabbypattyl <2531551606@qq.com> Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com>
1 parent 7378ec9 commit b96c48c

38 files changed

Lines changed: 6740 additions & 224 deletions

docs/design/cat-idle-states-feature.md

Lines changed: 279 additions & 0 deletions
Large diffs are not rendered by default.

main_routers/pages_router.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
2727
_YUI_GUIDE_ASSET_VERSION_PATHS = (
2828
_PROJECT_ROOT / "static/css/yui-guide.css",
29+
_PROJECT_ROOT / "static/css/index.css",
2930
_PROJECT_ROOT / "static/yui-guide-steps.js",
3031
_PROJECT_ROOT / "static/yui-guide-overlay.js",
3132
_PROJECT_ROOT / "static/yui-guide-page-handoff.js",
@@ -36,6 +37,30 @@
3637
_PROJECT_ROOT / "static/yui-guide-avatar-stage.js",
3738
_PROJECT_ROOT / "static/yui-guide-wakeup.js",
3839
_PROJECT_ROOT / "static/yui-guide-director.js",
40+
_PROJECT_ROOT / "static/app-auto-goodbye.js",
41+
_PROJECT_ROOT / "static/app-ui.js",
42+
_PROJECT_ROOT / "static/app-interpage.js",
43+
_PROJECT_ROOT / "static/common-ui-hud.js",
44+
_PROJECT_ROOT / "static/app-react-chat-window.js",
45+
_PROJECT_ROOT / "static/avatar-ui-buttons.js",
46+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat1.gif",
47+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat1-click.gif",
48+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat2.gif",
49+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat2-click.gif",
50+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat3.gif",
51+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat3-click.gif",
52+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat4-1.gif",
53+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat4-2.gif",
54+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat4-3.gif",
55+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat-move-1.gif",
56+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat-move-2.gif",
57+
_PROJECT_ROOT / "static/assets/neko-idle/cat-idle-cat-move-3.gif",
58+
_PROJECT_ROOT / "static/assets/neko-idle/cat1-voice-click.mp3",
59+
_PROJECT_ROOT / "static/assets/neko-idle/cat1-voice1.mp3",
60+
_PROJECT_ROOT / "static/assets/neko-idle/cat1-voice2.mp3",
61+
_PROJECT_ROOT / "static/assets/neko-idle/cat1-voice3.mp3",
62+
_PROJECT_ROOT / "static/assets/neko-idle/cat2-sleep.mp3",
63+
_PROJECT_ROOT / "static/assets/neko-idle/cat3-sleep.mp3",
3964
_PROJECT_ROOT / "static/css/character_card_manager.css",
4065
_PROJECT_ROOT / "static/js/character_card_manager.js",
4166
_PROJECT_ROOT / "static/css/card_maker.css",

plugin/plugins/galgame_plugin/screen_classifier.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,4 +671,3 @@ def _classified(
671671
raw_ocr_text=_bounded_raw_text(lines),
672672
debug=result_debug,
673673
)
674-

0 commit comments

Comments
 (0)