Эх сурвалжийг харах

docs(desktop-pet): 玄凤鹦鹉精细化动作设计文档

liaoxg 1 өдөр өмнө
parent
commit
e6238780dd

+ 118 - 0
docs/superpowers/specs/2026-10-12-cockatiel-actions-design.md

@@ -0,0 +1,118 @@
+# 玄凤鹦鹉精细化动作 设计文档
+
+- 日期:2026-10-12
+- 组件:`cfc-desktop-pet`(Tauri v2 桌面宠物)
+- 平台范围:**先 Windows**(代码跨平台可编译,macOS 坐标/多屏细化留后续)
+
+## 1. 需求(原文)
+
+> 实现宠物的精细化动作,比如对于玄凤鹦鹉,要能够走路,也能够跳着走。鼠标放在鹦鹉上时变成手指的样式,放在胸部 5 秒钟,鹦鹉会迈到手指上,然后手指鼠标移到哪儿,鹦鹉就跟到哪儿。鼠标抖动一下鹦鹉会飞到屏幕的任意地方。
+
+拆解为 4 个行为:
+
+1. **自主走 / 跳着走**:空闲时宠物会在屏幕上走动,或跳着走。
+2. **悬停变手指**:鼠标移到鹦鹉身上时光标变为手指(pointer)。
+3. **胸部按住 5 秒 → 站到手指上并跟随**:悬停鹦鹉胸部 5 秒,鹦鹉迈到"手指"(光标)上,之后跟随鼠标移动。
+4. **抖动甩飞**:跟随(站手)状态下抖动鼠标,鹦鹉飞到屏幕任意位置后落地。
+
+## 2. 决策(已与用户确认)
+
+- **动作素材:程序化动作**。当前 `src/DefaultPets/{xibao,fubao}/` 只有 `pet-config.json`,**无任何图片**,宠物实际渲染 emoji 兜底形象。因此不依赖精灵帧,改用「整体位移 / 弹跳 / 挤压拉伸 / 旋转 / 扑腾」等程序化姿态 + **移动窗口**实现动作。
+- **范围:先 Windows**。
+
+## 3. 核心架构
+
+宠物所在窗口仅 200×200 且透明置顶,**装不下跨屏走动/飞行**——所以"走/跳/飞/跟随"必须**移动窗口本身**。窗口位置只能由 Rust 侧修改,故分层:
+
+| 层 | 职责 |
+|---|---|
+| **Rust(移动者)** | 轮询全局光标 `cursor_position()`;按显示器 `current_monitor().work_area()` 夹取边界;移动窗口 `set_position()`;抖动检测;驱动 locomotion 状态机;通过 `pet://gait` 事件把姿态种类下发给前端 |
+| **JS(渲染者)** | 只渲染**局部姿态**(走路摆动、跳跃挤压、飞行扑腾、站手平衡),以及窗口内的悬停/胸部悬停判定与气泡 |
+
+选用理由:避免 JS 每帧 `invoke` 跨进程调用造成卡顿(方案 B 被否),不引入物理/动画库(方案 C 被否,YAGNI)。
+
+### 关键 Tauri API(已在本机 crate 源码确认存在)
+
+- `WebviewWindow::cursor_position() -> Result<PhysicalPosition<f64>>`(全局光标,无需全局钩子 → 免 macOS 辅助功能权限)
+- `WebviewWindow::set_position(Position)` / `outer_position()` / `outer_size()`
+- `WebviewWindow::current_monitor() -> Result<Option<Monitor>>`
+- `Monitor::work_area() -> &PhysicalRect<i32,u32>`(`position` + `size`)
+
+### 分层动画合成
+
+`#pet`(情绪动画,由 `pet-engine.js` 管理 `.anim-*`)内新增 `#pet-body` 包装层承载**姿态动画**(`.pose-*`)。两层 transform 独立叠加,互不覆盖。
+
+```
+#pet  ← 情绪:.anim-wings_idle / .anim-clap …(pet-engine)
+  └── #pet-body  ← 姿态:.pose-walk / .pose-hop / .pose-fly / .pose-perch(pet-pose)
+        └── #pet-img
+  └── #chest(脆弱热区:胸部 hover 判定)
+  └── #bubble
+```
+
+## 4. 组件设计
+
+### 4.1 Rust `locomotion.rs`(新)
+
+- 循环间隔 60ms。
+- 模式机:`Idle`(静立)→ 随机触发 `AutoWalk`/`Hop`;`Perch`(跟随)由命令进入;抖动触发 `Fly`。
+- 移动窗口:维护当前窗口物理坐标,按步进趋近目标;`Perch` 时直接 `窗口 = 光标 - 锚点`(锚点 = 宠物双脚在窗口内的坐标 ≈ (100, 200))。
+- 边界:一律 clamp 到 `current_monitor().work_area()`,保证整窗可见。
+- 抖动检测:保存最近 N 个光标采样点(位置+时间),在 ~600ms 窗口内统计方向反转次数与总位移;满足阈值 → `Fly`。
+- `Fly`:约 600ms 内插值移动到工作区内**随机目标点**,落地回 `Idle`。
+- 事件:`pet://gait {kind, direction, phase}`(kind ∈ idle/walk/hop/fly/perch)。
+- 事件:`pet://perch {active}`(进入/退出跟随,供前端同步"是否站手")。
+
+### 4.2 Rust `state.rs`(纯函数 + 单测)
+
+新增可单测的纯逻辑(不依赖 tauri 运行时):
+
+- `detect_shake(samples, now) -> bool`:方向反转 + 位移阈值。
+- `clamp_window_pos(pos, work_area, win_size) -> (i32,i32)`:把窗口左上角夹进工作区。
+- `pick_random_target(work_area, win_size, seed) -> (i32,i32)`:工作区内的随机落点(用简单 LCG,便于测试确定性)。
+
+### 4.3 Rust `app_state.rs` / `commands.rs` / `main.rs`
+
+- `AppState` 增 `follow_cursor: AtomicBool`(命令与循环共享)。
+- 命令 `start_perch`(胸部悬停满 5s 时前端调用)→ 置 `follow_cursor=true`,emit `pet://gait {kind:"perch"}` + `pet://perch {active:true}`。
+- 命令 `stop_perch` → 置 false,emit `pet://perch {active:false}`。
+- `main.rs`:注册新命令;`setup` 内 `locomotion::spawn(...)` 启动循环(与 `poller::spawn` 并列)。
+
+### 4.4 JS
+
+- `index.html`:新增 `#pet-body` 包装、`#chest` 热区;`#pet { cursor: pointer }`(手指);引入 `pet-pose.js`;`#chest` 定位在鹦鹉胸部(上方中区)。
+- `animations.css`:新增 `@keyframes walk_bob / hop / fly_flutter / perch_lean` 与 `.pose-*` 类。
+- `pet-pose.js`(新):监听 `pet://gait` 切换 `#pet-body` 的姿态类;监听 `pet://perch` 设置 `window.__petPerched`。
+- `bridge.js`:`cfcHost` 增 `startPerch()` / `stopPerch()` → `invoke('start_perch'|'stop_perch')`。
+- `interactions.js`:
+  - `#chest` `mouseenter` 起 5s 计时 → `cfcHost.startPerch()`;`mouseleave` 前取消;成功后面板气泡「飞到你手上~」。
+  - `__petPerched` 为真时跳过 `drag_start`(避免跟随与原生拖拽打架)。
+
+## 5. 交互映射
+
+| 操作 | 结果 |
+|---|---|
+| 空闲 | 偶发 `walk` / `hop`(窗口横移;hop 带弹跳) |
+| 鼠标移到鹦鹉上 | 光标变手指(pointer) |
+| 悬停胸部 5 秒 | 迈到"手指"上 → 跟随鼠标(窗口跟随光标,双脚落在光标处) |
+| 跟随中抖动鼠标 | `fly` → 飞到当前显示器工作区随机点 → 落地回 idle |
+
+## 6. 边界与冲突
+
+- 多屏/边界:一律以 `current_monitor()` 工作区为准 clamp。
+- 「胸部悬停」用**纯 hover**(不按键),与「按住拖拽」互不冲突;站手期间禁用原生拖拽。
+- 窗口保持置顶;跟随由 Rust 轮询驱动,不依赖窗口事件,避免光标快速移动导致丢失。
+- `set_ignore_cursor_events`(鼠标穿透)与移动窗口互不影响。
+
+## 7. 验收
+
+- Rust:`cargo test` 覆盖 `detect_shake` / `clamp_window_pos` / `pick_random_target`,全绿。
+- Rust:`cargo check --all-targets` 通过。
+- JS:全部 `.js` `node --check` 通过。
+- 画面:由用户运行新构建 exe 肉眼确认(本机 WebView2 CDP 不可用,无法自动截图)。
+
+## 8. 非目标(本轮)
+
+- 真实多帧精灵动画(待美术素材到位后,按 `pet_config.animations.frames` 接入)。
+- macOS 多屏/Retina 坐标细化。
+- 宠物沿任意曲线路径行走(本轮为水平步进 + 弹跳)。