- 106 动画 + 2366 表情包 + 语义标签 - 三层记忆 / 心情系统 / 9 可配置插件 / 10 技能 - DeepSeek 对话直连 + MCP/HTTP 接入 + 接入文档 - 动画随机链 + 物理引擎 + 贴边碰撞校准 - 大资源(动画webp/表情图)由 scripts/ 再生成,不入库
9.2 KiB
悬浮宠物(com.dsh.petfloat)工程备忘
用户偏好(重要)
- 默认一律构建并交付「原画质」包(640×360,
-Phq),不要再默认出小体积包。 - 原因:pilot 的
app.install有 128MB 硬上限(见/opt/pilot/USAGE.md), 原画质包 ~427MB 无法通过 pilot 安装 → 由用户手动安装。 - 小体积包(320×180,~118MB)仅作为「需要我远程直装调试」时的临时手段,不作为交付物。
素材目录(已拆分)
| 目录 | 内容 |
|---|---|
app/src/main/assets_common |
config.json、candy.ttf、pet.html、m01..m27.webp(表情包) |
app/src/main/assets_small |
a0001..a0106.webp(320×180, q68) |
app/src/main/assets_hq |
a0001..a0106.webp(640×360, q80,原画质) |
app/build.gradle.kts 按 -Phq 选择 small / hq:
# 原画质(交付用)
JAVA_HOME=/opt/jdk17 /opt/gradle/gradle-8.5/bin/gradle assembleDebug -Phq
# 小体积(仅用于 pilot 直装调试)
JAVA_HOME=/opt/jdk17 /opt/gradle/gradle-8.5/bin/gradle assembleDebug
产物统一复制为 petfloat-hq.apk / petfloat-small.apk,HQ 通过 present 交给用户手动安装。
动画素材来源
/root/projects/.dsh/profiles/web/node_modules/dsh-pet/assets/webm/*.webm(106 个,VP9-alpha,10.042s;
「工作状态-垂头叹气冒汗」为 4.917s)。
转码(本机 ffmpeg 的 webp 解码器不支持动画 webp,只能编码,校验用 RIFF 解析):
ffmpeg -y -c:v libvpx-vp9 -i in.webm \
-vf scale=640:360 -c:v libwebp_anim -q:v 80 -loop 0 -pix_fmt yuva420p -an out.webp
已知限制
app.install单文件 ≤128MB,且每次都要用户在手机确认。- pilot 审批卡只在 60 秒窗口内有效;安装前需用户在看手机。
- 动画池/权重/物理参数全部来自
assets_common/config.json(由 dshconfig.jsonc生成)。
对外接入端口(供 workbuddy / DeepSeek 等调用)
本机 HTTP 服务监听 127.0.0.1:7350(仅回环,设备内任意 App 可访问)。
控制面板「接入」页可查看地址并复制。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/health |
返回应用信息与端点清单 |
| POST | /api/notify |
{state|level, text, durationMs, meme} 推通知 |
| POST | /api/task |
{state: start|working|result|waiting|done|error, text} |
| POST | /api/anim |
{name, holdMs?} 点播任意动画(中文名) |
| POST | /api/broadcast |
{text, duration, meme} 仅气泡 |
| POST | /api/clear |
清空气泡 |
| GET | /api/state |
当前动画与气泡状态 |
状态 → 动画档位:start/thinking→思考冒泡,working→忙碌点按,result→清点归档,
waiting→原地踱步张望,done/success→雀跃庆祝,error→垂头叹气冒汗。
level 取值 info/success/warn/error/working/result。
通知会设置 holdUntil,随机动画链在展示时长内不会覆盖它。
示例:
curl -X POST http://127.0.0.1:7350/api/notify \
-H 'Content-Type: application/json' \
-d '{"state":"done","text":"任务完成:报告已生成"}'
调用方需允许明文 HTTP(usesCleartextTraffic 或 network-security-config 放行 127.0.0.1)。
气泡(独立悬浮窗)
- 气泡是独立 overlay 窗(
FLAG_NOT_TOUCHABLE),浮在宠物窗口上方, 位置 = 宠物窗口顶边 - 气泡高度 + 6dp,水平居中,自动夹在安全区内;宠物移动时跟随。 - 因此气泡不会被宠物窗口裁切(此前 240×135dp 窗口内的 HTML 气泡会被截断)。
- 样式存
pet_prefs/bubble_style,可选default|dark|outline|note, 由BubbleDrawable(圆角矩形 + 底部三角尾巴)绘制;面板「外观 → 气泡样式」切换。 - 宠物窗口 HTML(pet.html)现在只画宠物本体。
自助接入文档(端口自带)
GET /docs—— 人看的 HTML 文档页:地址/鉴权/在线测试表单/接口表/状态映射/错误码/代码示例GET /api/docs—— 机器可读 JSON 规范:endpoints[] / states / levels / errors / auth- 已支持 CORS 预检(OPTIONS → 204,允许
Content-Type, X-Pet-Token), 浏览器/WKWebView 类应用可直接 fetch 调用。
互动事件流(SSE)
GET /api/events?token=<授权码> 返回 text/event-stream,每 15s 心跳 : ping。
事件类型(event: pet,data 为 JSON):
| type | 触发 | 额外字段 |
|---|---|---|
| tap | 用户点按宠物 | anim |
| drag_start | 开始拖拽 | x, y |
| drag_end | 松手 | fling(bool), x, y / vx, vy |
| long_press | 长按打开面板 | - |
| play | 点播动画(面板或 /api/anim) | name |
| notify | 推送通知 | state, level, text |
| bubble | 只发气泡 | text |
| bubble_clear | 清空气泡 | - |
| anim | 随机链换动作 | name, kind |
EventSource 不能设请求头,所以 SSE 用 ?token= 传授权码。
应用主界面(MainActivity)
- 以前 MainActivity 启动服务后立刻
finish(),点图标「像没反应」。现在改成真正的首页: 状态卡(运行中/未运行 + 当前动作 + 订阅数 + 是否需授权码)、显示/收起宠物、 控制面板入口、复制接入地址、打开接入文档、最近通知(4 条)、版本信息。 - 首次进入自动拉起宠物(需已授予悬浮窗权限,否则显示授权入口)。
- 主题改为
Theme.Material.Light.NoActionBar。 - 状态通过轮询
/api/health+/api/state获取(2s 一次,页面不可见时停止)。
交付方式:手机共享目录(推荐)
/root/share/DSHBox 是手机内部存储 DSHBox/ 的 bind 挂载(/storage/emulated/0/DSHBox),
构建好的 APK 直接放进去,手机上用文件管理器点开即可安装,不受 pilot 128MB 限制。
./build.sh # 原画质 → petfloat-hq.apk,并自动复制到 /root/share/DSHBox/
./build.sh small # 小体积 → petfloat-small.apk
手机端路径:内部存储/DSHBox/petfloat-hq.apk(首次安装需允许「安装未知应用」)。
界面合并(v1.0 后期)
- SettingsActivity 已删除,所有设置并入
MainActivity的 4 个页签: 状态 / 外观 / 动作 / 接入。 - 长按宠物 → 直接打开
MainActivity(默认落在「外观」页,EXTRA_TAB=TAB_LOOK)。 - 点通知 → 打开
MainActivity(「状态」页)。 MainActivity为launchMode=singleTop,重复打开走onNewIntent切换页签。- 授权码仍在「接入 → 推送授权」列表的二级弹窗里查看/复制/改名/删除。
UI 方向(最终)
- 参考 DeepSeek App:白底分组列表(Inset Grouped)+ 细分割线 + 右侧 chevron + 品牌蓝
#4D6BFE。 - 不再做液态玻璃(试过 A/B/C 三版 + 奶白玻璃,用户否决)。
- 三个页签:状态 / 动作 / 设置(底部浮动胶囊,纯文字,选中为浅蓝底 + 品牌蓝加粗)。
- 跟随系统浅色/深色(
Configuration.UI_MODE_NIGHT_MASK,浅 #F7F8FA/白卡,深 #121316/#1B1D21)。 - 字体统一用系统字体,只有品牌大标题可用软糖体。
- 长按宠物 → 主界面「设置」页;点通知 → 「状态」页。
碰撞边界(重要)
getRealMetrics取真实屏幕(1440×3200),且系统对悬浮窗有offsetY=169px(状态栏)偏移, 用getLocationOnScreen自校准得到offsetX/offsetY,碰撞边界据此补偿。- 素材本身带大量透明留白(实测 l≈33.8%、r≈34.1%、t≈17.8%、b≈11.7%),
所以窗口贴边时角色仍离边很远。解法:
pet.html用 canvas 扫码 alpha 通道量出边距,POST /sprite上报(免鉴权,服务端取各动画的最大值),Service 允许窗口超出屏幕这些距离, 使角色真正贴边。 - 旋转/折叠由
onConfigurationChanged重算边界并夹回。
物理引擎(v2)
- 真实帧间隔积分(
System.nanoTime,dt 上限 0.05s 防卡顿跳跃) - 半隐式欧拉 + 空气阻力(0.55/s)
- 重力按密度换算:
gravity × density × 0.85(跨机型手感一致) - 分面弹性:地面
restitution,墙面0.82×restitution - 地面摩擦衰减(
groundFriction指数衰减),低速自动收敛停止 - 飞行中按水平速度轻微倾斜(±11°),落地回正
- 撞击(>450px/s)触发挤压&拉伸形变(1.14/0.86 → 1.0,240ms)
- 速度上限 5200/7000 px/s,避免高速穿透
- 全面使用配置里的 throwPower / restitution / groundFriction / ceilingBounce
记忆(三层,纯本地)
- L1 短期:最近 20 轮原文(chat_history,存 40 条)
- L2 长期记忆卡:
MemoryStore(JSON 存 pet_prefs.memory_cards,上限 300 条) 字段:title / content / tags / importance(1~3) / created / updated / hits - L3 召回:关键词切分命中(2.0/词) + 标签命中(1.5) + 重要度(0.8×i) + 新鲜度(≤1.2) + 使用次数(0.15×hits),取 Top5 注入 system 第二段
- 抽取:每 2 轮对话异步调一次
ChatClient.extractMemories(单独 prompt,输出 JSON,失败静默) - 管理:设置 → 扩展 → 记忆(自动记忆开关 / 长按删除 / 手动添加 / 清空)
- 不走 embedding:DeepSeek 无 embedding API,本地向量模型会让 APK 膨胀