← reports index
验收单 · 2026-09-06

2026-09-06 改动验收单 —— 归位、跑测面板、杯位分布

今天一共改了三块东西。每一块下面都写了:改了什么 · 我这边已经用什么证据验过 · 你要按哪几步验收 · 验收通过长什么样。凡是我这边验不了、必须你在浏览器里看的,都单独标出来了。

先记住三条硬规则(今天踩出来的):
① 页面改过就要硬刷新(Ctrl+Shift+R / 手机关标签重开),否则跑的是旧 JS;
② 你在跑测时我不会再重启 8770 —— 今天两次重启把阶段打成 orphan、横幅全变,是我造成的干扰;
③ 面板行为查不清,先看 journalctl --user-unit=robot-status | grep '\[panel:',页面每次决定画什么按钮都回传一条信标,不猜。

一、归位:开跑前把手臂放回示范起点

改了什么

文件内容
03-software/scripts/arm_home.py(新)独立模块。起点读死、按墙钟时间插值(smoothstep,首尾速度 0),峰值 22°/s,终点连发 0.6 s,到不了位打 没到位。左/右/双臂由配置决定;录制时没上电的手臂(全 0 且从没变过)识别为不可用,不碰它、维持原姿。
03-software/configs/trial_start_pose.json(新)示范起点 = 40 集每集第一帧的中位数,带数据集出处。换数据集用 arm_home.py --recompute <数据集目录> 重算。
run_policy_trials.py连上后归一次位 + 每次试验开跑前再归一次;结果 JSON 每次记 homed: true/false;--no-home-start 关。面板「跑测参数」里有开关,默认开。归哪条手臂、归到哪:按这个模型训练用的数据集算(每集第一帧的中位数,缓存到 configs/trial_start_pose-<数据集>.json)—— 右臂模型归右臂(0826:肩抬 −17.1°),左臂模型归左臂(sep3-clean:肩抬 −29.5°),录制时没上电的那条不碰。另:--help 原来会崩(帮助文本里的 %),顺手修了。
为什么第一版没动:写成「每拍从实测位置 +0.75°」。负重手臂克服不了静摩擦,舵机不动;下一拍又从同一个实测位置算,目标永远不累积 —— 两次归位返回的六个关节角完全相同。同样写法在 leader-follower 里能用,因为那边动的是被牵引的 leader 臂(几乎不承重)。

我已验的证据

验证结果
arm_home.py --selftest(带静摩擦模型的假机器人)8/8 PASS:到位偏差 0.99°、峰值 22.0°/s、起步 0.3°/s、左臂/头/底盘一个数不动、双臂各到各位、dry run 一帧不发、到不了位会报、缺维度拒绝、左臂识别为不可用
arm_home.py --arm right --go(真机)到位:shoulder_lift +51.3° → −17.1°,68.4° 行程 4.7 s,最大偏差 2.29°(容差 3°)

你要验收的

1
面板起一批实跑(次数选 1),按确认。
预期:日志出现 [home] right span 6x° over 4.xs 然后 [home] 到位,最大偏差 x.x°;手臂真的抬到示范起点(肩抬 −17°,肘 74°,腕 −59°)。如果出现 ! 没到位,把那行发我。
2
按「开始这一次」。
预期:再归一次位(手臂已在位,几乎不动),然后策略接管。跑完看结果 JSON 里 "homed": true。

二、跑测面板:按钮必须跟着进程走

今天到底出了什么问题(信标抓到的,不是猜的)

根因:opTrialsTick 在 await 之后写着 if (gen !== OP_TR_GEN) return;,而模块每 3 秒重画一次就换一次代号。你在国内经 Tailscale 访问,一次往返常超过一秒 —— 请求还没回来代号已经换了,取回来的数据整个丢掉,没有任何一跳活到渲染那一行。面板永远停在最后一次成功的那屏,没有报错、没有异常,本机毫秒级延迟永远复现不了。journal 里那串每秒一条的 watchdog: panel untouched for 46s…85s 就是现场。
你看到的机制修法
确认/开始按钮不出现;卡片停在上一状态代号竞态丢弃新鲜数据(上面那条)新鲜数据一定画;代号只决定续不续链;Ctl(true) 只在链 5 s 无心跳时才换代号
同上渲染一抛,下一行的 setTimeout 到不了,链静默死finally 续链;渲染整段 try,抛了画红框 + 回传
同上模块门一失败 OP_TR_TICKING=false,链永不复活门只跳过渲染不杀链;看门狗按心跳复活
状态到 TRIAL 1/1,按钮还是「确认」d.context 没查 null,抛在两块 DOM 之间已护;selfcheck 新规则 ⑥ 专抓这类(今天扫出 6 处同形状,全补)
进程退了,底部条还挂着 成功/失败/不计底部条被 tctlRender 顺带驱动,那条链停了底部条独立定时器驱动,取不到数据 = 没按钮
过期按钮按下去「像生效了」服务端不校验409 闸:/api/trials/control 只放行进程此刻在等的命令(abort 例外),页面弹「进程现在在等 X」并重画
横幅变成「面板服务重启过」我在你等门时重启了 8770不再这么做

我已验的证据

验证结果
trials_gate_selftest.py(起真实空跑走完全部状态)20/20 PASS:无进程 → 全 409;等开始 → 只放 start;运行中 → 全 409;等判定 → 只放 s/f/x;退出后 → 409
selfcheck.py(HTML 配平 / JS 语法 / 作用域泄漏 / 负载裸取 / 下发一致)全过,dashboard 与 data 两页
浏览器里按钮跟着状态走待你验 —— 这是我这边唯一验不了的

你要验收的

3
硬刷新主面板,停在操作页 → 试跑模块。
预期:底部没有操作条(没进程);journal 里不再出现 watchdog 行。
4
点「空跑 1 次」(手臂不动),一路等到它要你按「开始这一次」。
预期:按钮区在1 秒内从「启动按钮已收起」变成「▶ 开始这一次」,底部同时弹出同一颗按钮。journal 出现 [panel:op] phase=ready waiting=[start] running=true btn=true。
5
按开始,等它跑完到判定。
预期:按钮区只剩 ✅ ❌ —,底部条同步;journal waiting=[f,s,x] btn=true;没有 ASSERT buttons≠waiting 行。
6
故意用命令行发一个过期命令:
curl -s -X POST http://127.0.0.1:8770/api/trials/control \
  -H "X-Auth-Token: $(cat ~/.robot_server_password)" \
  -H 'Content-Type: application/json' -d '{"cmd":"confirm"}'
预期:HTTP 409,返回 "waiting_for": ["f","s","x"]。这就是「过期按钮按了也不会误触发」。
7
按「— 不计」,等进程退出。
预期:底部条在 3 秒内消失,按钮区回到「空跑 / 实跑」两颗。
命令行路径永远可用,和按钮走同一个接口:confirm / start / s f x / abort。

三、杯位分布:并进数据页总表

改了什么

文件内容
demo_coverage.py返回逐集的点(points,含全链 FK 的指尖坐标)+ 两条手臂的参照(reach:肩轴、伸直平放时肩→肘→腕→指尖的骨架、指尖最远触及弧)。按真正在动的那条手臂算(夹爪开度方差),原来右臂下标写死,左臂数据集 54 集缩成一个点。
robot_server.py新接口 /api/demo_coverage?ds=<短名>;另加 /api/jserr(页面报错回传)。
robot_server_data.html总表每行一颗 📍 杯位,展开在该行下画该数据集的分布。单独标签删了。
配色是算出来的不是挑的:四个杯位四种颜色在暗色模式下过不了色盲分离硬地板(蓝 vs 紫 ΔE=1.9),改单系列一色,分组用簇顶 n= 标注。这台机器没有 node,官方验证器移植成了 .claude/skills/self-debug/validate_palette.py。

你要验收的

8
打开 /data(刷新),在总表里点 xlerobot-left-pick-cup-sep3-clean-20260905 那行的 📍 杯位。
预期:标题「干活的是左臂 · 54 集」;54 个点散开(横向跨度约 23 cm),不再是一个点;图上有两条手臂骨架(■肩轴 → ○肘 → ○腕 → ○指尖,正前方加粗带字,±60° 淡)和虚线触及弧;还有一条强调色的「示范起点」骨架(每集第一帧中位数,就是试跑归位的目标)和每集起点指尖的小空心点,侧栏写着肩抬/肘/腕的角度。
9
再点 xlerobot-right-pick-cup-20260826-0042 那行。
预期:「干活的是右臂 · 40 集」,点落在右臂骨架前方、贴近触及弧的一带。
10
看四个合并数据集那几行(lr-merge-20260904 202 集、lr-sameview-20260904 100 集、lr-clean-20260905 94 集、sep3-clean-20260905 54 集)。
预期:每行都有「🔗 合并自 N 个」,每个子集名是链接,点了跳到该子集那一行并高亮(被筛选/归档藏着的会自动放开);「详情 ▾」展开一张表:每个子集的集数 / 帧数 / 语言指令 / 位置 / 健康,末行核对「子集之和 = 合并后」。

这些 100 集、202 集是哪来的

都是面板「合并数据集」作业(aggregate_datasets.py)产的,来源写在每个数据集的 meta/merge_sources.json 里,现在页面上直接能点开看。时间线(芬兰时间):

数据集集合并时间由什么合并
lr-merge-2026090420209-04 17:39右臂 0826(40)+ 右臂 sep2 四批(51)+ 左臂 sep2 两批(51)+ 左臂 sep3 六批(60)= 全部
lr-sameview-2026090410009-04 17:47右臂 0826(40)+ 左臂 sep3 六批(60)—— 去掉了 sep2(那批头是抬着的,视角不同)
sep3-clean-202609055409-05 01:35左臂 sep3 五批(去掉 1824 那批 6 集)
lr-clean-202609059409-05 01:45右臂 0826(40)+ sep3-clean(54)

四个的「子集之和 = 合并后」全部对得上,没有合并时静默丢集。

四、一次试跑的数据是怎么说的(顺带)

今天 16:58 那一次(判据错、标 void):安全层 46/46 放行、拒帧 0;光照 G/R 0.94 与白天数据集一致;头部姿态与训练集差 <0.6°;全程关节值都落在训练集范围内。唯一对不上的:起始 shoulder_lift = +21.03°,而 40 集示范起点最高只到 +13.41°。指尖 19.5 秒总位移 3.2 cm,夹爪来回开合,没朝杯子去 —— 这就是「归位」这块工作的由来。证据等级:1 次,void,只能说这一次长这样。

五、还没做 / 你要决定的

改动文件:arm_home.py(新)· trials_gate_selftest.py(新)· configs/trial_start_pose.json(新)· run_policy_trials.py · robot_server.py · robot_server_dashboard.html · robot_server_data.html · demo_coverage.py · .claude/skills/self-debug/{selfcheck.py,validate_palette.py,SKILL.md}。所有数字均为 2026-09-06 本机实测。