V0.3 — 从"技术仪表盘"变成"产品界面"

v0.2 以"技术跑通"为导向,UI 是 Anima 仪表盘 + 仿真窗口的四列布局。v0.3 的目标是把信息层级反过来:让访客第一眼看到的是产品(护理机器人在病房里做事),而不是技术架构。Anima 的 L0–L5 细节退居可折叠抽屉,供想看技术的人主动展开。

UI 从暗色技术风改为浅色临床风

  • 主题从 zinc-950 暗色改为白底 + zinc-200 边框,更像真实临床产品的界面
  • 新增 bci/ 组件组:SessionHeader / ChannelHealth / NeuralActivity / DecoderOutput / FiringRateBars —— 都标注 decorative,和 v0.1 定下的"不伪装神经解码"诚信线一致
  • 新增 shell/ 组件组:MissionRibbon(顶部任务条)/ BciDrawer(左抽屉)/ TaskDrawer(右抽屉)—— 抽屉默认收起,把"产品视图"让给 MJPEG 主画面
  • SimulationView 升级为多相机 + E-stop + 状态轮询的产品级组件

后端:五路相机渲染管线

从单路 demo_view 扩到五路:demo_view / grasp_view / bedside_view / top_down / tv_view

技术细节:

  • _CameraFeed 类,每路一个 mujoco.Renderer + 自己的 latest_jpeg + condvar
  • 单个渲染线程从一份共享 MjData 同步刷所有 feed
  • /api/sim/cameras 路由广告相机列表,/api/sim/mjpeg?camera=X 按名取流
  • 关掉每个 renderer 的 mjVIS_RANGEFINDER 标志(否则地面会被黄色测距光束糊满)

E-stop 旁路通道

第一条独立于 LLM / 行为树的紧急停止通道:

  • SimManager.estop_activethreading.Event 暴露全局 flag
  • POST /api/sim/estop 设 flag 并把底盘速度归零;POST /api/sim/estop/clear 清掉
  • SimSkillBehaviour.update 每 tick 第一件事检查 flag → 立即 FAILURE,不走 LLM / BT 重建
  • Reset Sim 同时清 E-stop flag
  • 前端右上角红色按钮;激活时变 amber "Clear E-stop",顶部出现红色警示条

这是后来 v0.4 规划"三种非信号通路旁路"的第一条原型。

六个意图全部跑通

每条都给出可演示动作:

  • DRINK_WATER — 五步抓杯送床边(沿用 v0.2)
  • ADJUST_TV — TV 屏的 emission 材质变亮蓝
  • CALL_HELP — 机器人抬臂做信标 + 护士 mocap 出现在病床旁停留 6 秒后归位,同时 WebSocket 推 help.called 事件
  • GOTO_BED — base 驱到床头位 (4.5, 1.3, -π/2)
  • TURN_OFF_LIGHT / TURN_ON_LIGHT — 五盏光源 diffuse 清零或恢复 + 吊灯发光材料置黑或亮。关键细节:subtask 名 turn_off_light / turn_on_light 决定目标态,不是纯 toggle,避免"再说一次关灯反而开了灯"的语义错误
  • E-stop 中点 → outcome=cancel

踩过的坑(值得写下来)

床头位避障:最早把 BEDSIDE_POSE(4.2, 1.0, -π/2),unicycle PID 在 (3.7, 1.0) 附近卡住反复震荡 50+ 秒到超时。根因是 nightstand 碰撞箱的 y 边到 0.79,Stretch 底盘半径约 0.35,机器人中心 y < 1.14 就会卡在 nightstand 北缘。改到 y=1.30.16m 安全余量,一次到位。

渲染性能天花板:Hetzner CPX31 没 GPU,osmesa 软件渲染五路相机满载 ~0.4 fps。直接影响是任何由 BT 驱动的"滑入式"动画(比如最初设计的"护士从门口走入")在渲染流上只采样到 1–2 帧,视觉上等同于瞬移。解决方式是把动画改成"目标态保持 ≥ 渲染周期"的离散关键帧——CallHelpSkill 的 6 秒停留就是为此设的。这条硬约束直接促成了 v0.4 放弃 Hetzner live demo,改为本地 Mac Metal 渲染 + 离线视频交付

Canonical Subtask Safety Net

l1_parser.py 加了一份 _CANONICAL_PLANS:每个 intent 都有一份确定性的 BT 兜底计划。LLM parse 失败也能跑——演示场合下要的是"永远别卡住",而不是"永远用最优路径"。

验收(已通过)

  • /api/sim/cameras 返回五个相机
  • tv_view MJPEG 200 OK、约 50KB/帧
  • 连续两次 /api/sim/reset 不再 SEGV(早期版本因为渲染线程和 _model 生命周期竞争过,修复见 SimManager.stop()
  • 六个意图在前端逐一跑通 outcome=success,E-stop outcome=cancel

v0.3 → v0.4 的移交

v0.3 是研究原型时期的最后一个版本,交付形态仍然是 Hetzner 上的 live demo。接下来 v0.4 起在 jeffliulab/soma-care 独立仓推进,重点转向本地高帧率渲染 + 离线视频 + 更细致的接触密集操作。原 Hetzner live demo(89.167.35.145)保持可访问,但不再作为主展示。