学习 / 实践

具身实践(四):消息还在到,观测已经过期了

具身智能 · 工程案例

45回合MuJoCo对照,分离采样间隔、异常计数与观测年龄;理解旧消息为何会掩盖真实松爪,以及时间阈值仍有什么局限。

进阶 · 14 分钟 · 更新于 2026-09-29

实验结果

浏览统计暂不可用

本文目录 9
跳到正文 ↓

上一轮已经让程序在搬运过程中持续检查接触:连续三次异常,就取消后续搬运。但有两个问题还没解决。

如果把传感器从每 20 ms 更新一次改成每 50 ms 一次,“三次”还是同一个等待时间吗?如果网络一直传来“接触正常”,却反复传的是几十毫秒前的旧消息,程序应该相信它吗?

这次用 45 个真实 MuJoCo 仿真回合做了对照。最直接的观察是:同一个三次阈值,报警等待从 22 ms 变成了 102 ms;重送旧正常包时,只看接触内容的策略一直没有报警。补上捕获时间检查,才识别出证据已经过期。

配套代码 · 原始记录与审计 · 实验档案

1. 先看一个实际失效过程

下面摘取 20 ms 采样、重送旧正常包条件中的记录。仿真在 4.800 秒开始强制松爪,但传输层继续递交之前的包。表中秒数由日志 tick × 0.002 换算。

接收时刻 包序号 原始捕获时刻 包内双侧接触 物理双侧接触 观测年龄
4.782 s 239 4.782 s 有 有 0 ms
4.802 s 239 4.782 s 有 无 20 ms
4.822 s 239 4.782 s 有 无 40 ms
4.842 s 239 4.782 s 有 无 60 ms

消息没有中断,内容也没有变成“异常”。发生变化的是:当前时间一直往前走,支持“接触正常”的那份证据却停在 4.782 秒。

三次计数策略没有看到坏接触,40 ms 异常跨度策略也没有看到。带新鲜度检查的策略在最后一行报警,原因是 stale,而不是 contact。

这一点很重要:年龄报警只证明“没有足够新的观测支持继续执行”,并不单独证明方块已经掉了。这次方块确实掉落,是另一条物理记录给出的证据。

2. 把采样、接收与控制拆开

复用原有零偏移场景、夹爪、动作时序和搬运前确认。所有回合都通过了前置检查,没有改变拾取难度。

时间尺度 本轮设定 负责什么
物理与控制步长 2 ms 推进物理、读取输入、检查年龄、输出下一步目标
观测包周期 10 / 20 / 50 ms 产生新的接触观测
常规轨迹记录 20 ms 保存位置、速度与控制状态
搬运决策记录 2 ms 记录每次收包、年龄、计数和报警

新鲜度检查必须在没有消息到达时也能运行。如果只把它写在接收回调里,完全静默时就没有机会检查超时。本例在每个控制步执行检查;这不是对真实操作系统实时调度能力的保证。

本轮使用同一个仿真时钟,时间以整数 tick 比较。MuJoCo 的时间、物理状态与控制输入本来就是不同字段,因此可以分别记录,而不是把“发送指令”当成“动作已经完成”。MuJoCo 3.3.7 状态与控制说明

3. 三种策略,分别补一条约束

策略 判定规则 新增约束
count3 连续 3 个唯一坏观测 基线
elapsed40 连续坏观测的首尾捕获时间相差至少 40 ms 把样本数量改成时间跨度
fresh60 elapsed40,再加最近观测年龄达到 60 ms 就报警 不把旧正常消息当当前证据

三者都只接受序号和捕获时刻同时前进的包。重复包不会增加异常计数,也不会重置“最近捕获时刻”。正常的新观测会清空异常累计,报警后保持锁存。

elapsed40 并不是“坏了一次就启动一个 40 ms 闹钟”。它需要新的坏观测来确认跨度:如果后面完全没消息,只靠等待不能补出“持续异常”的证据。fresh60 另设年龄检查来处理这种未知状态。

实现中的关键关系是:

# now 和 capture_tick 使用同一仿真时钟;一个 tick = 2 ms
age_ticks = now - latest["capture_tick"]

# 收到新包后先更新 latest,再检查年龄
if policy == "fresh60" and age_ticks >= 30:
    alarm = True
    cause = "stale"

源码还处理了无初始观测、重复包、乱序包、未来时间戳和阶段切换。这里摘录的是年龄分支,不是可直接替换整个监测器的完整程序。60 ms 是实验参数,没有经过硬件安全评估。

4. 45 回合到底改变了什么?

矩阵是 3 个观测周期 × 5 个输入条件 × 3 种策略。每格一次确定性运行,没有随机化、置信区间或概率成功率。

条件 物理输入 传给监测器的消息
clean 无扰动 正常更新
empty-40ms 无扰动 [4.8, 4.84) 秒内应发出的包报告空接触
forced-open [4.8, 5.04) 秒强制松爪 正常报告实际模拟接触
replay-good 同样强制松爪 4.8 秒起重复最后一个旧正常包
silence 同样强制松爪 4.8 秒起不再送达消息

empty-40ms 是内容错误,silence 是没有消息;replay-good 则是消息在到、信息没更新。三种情况不能用“没检测到接触”一句话混在一起。

故障发生在 pre-step 时间,首个对应的 post-step 记录是 4.802 秒。传输故障持续到回合结束,强制松爪只持续 0.24 秒。控制器不修改方块坐标,掉落来自模拟动力学。

报警后的处理沿用上一轮:保持最后命令目标 0.6 秒,再结束回合。不冻结物理状态,不重新抓取,也不宣称硬件急停。

5. 三次异常,不是固定等待时间

受控松爪且消息正常更新时,得到以下结果。时间从 4.800 秒的故障起点计算:

观测周期 count3 elapsed40 fresh60
10 ms 22 ms 42 ms 42 ms
20 ms 42 ms 42 ms 42 ms
50 ms 102 ms 52 ms 52 ms

同一故障相位下,计数策略的报警等待随采样周期增大;时间跨度策略仍有采样量化

三次连续样本的首尾跨度是两个采样间隔,即 (3 - 1) × 周期。再加本次从故障到首个样本的 2 ms,分别得到 22、42、102 ms。

对于 elapsed40,10 和 20 ms 周期都能在首个坏样本后的 40 ms 得到新证据;50 ms 周期必须等下一条,所以实际等了 50 ms,再加前面的 2 ms。

用时间表达阈值,减少了它与采样频率的耦合,但不会消除采样量化。 上表也不能视为任意故障相位的最坏延迟;本实验的故障起点固定,没有扫描相位、调度抖动或通信延迟。

6. 更快的采样也可能改变误停结果

在持续 40 ms 的空接触报告条件中,只有 10 ms 周期的 count3 误停,其余八格完成了放置。

原因可以直接从样本时刻读出:

10 ms 周期:4.802、4.812、4.822、4.832 为坏观测
20 ms 周期:4.802、4.822 为坏观测
50 ms 周期:4.802 为坏观测

10 ms 情况在第三个坏观测就满足了 count3;同样时长的报告错误,在另外两个周期下没有凑够三个。elapsed40 看到的坏观测首尾跨度最多为 30 ms,下一条已经正常,因此没有报警。

这不是“采样越慢越安全”。降低频率同时可能推迟真正故障的发现。它说明:如果允许传感器频率变化,计数阈值也隐含改变了系统对短暂异常的容忍时长。

7. 为什么慢采样反而更早触发年龄报警?

重送旧正常包和完全静默两种条件中,count3、elapsed40 均未报警。fresh60 的记录如下,两种传输故障结果相同:

观测周期 最后一次有效捕获 达到 60 ms 年龄时报警 从故障到报警
10 ms 4.792 s 4.852 s 52 ms
20 ms 4.782 s 4.842 s 42 ms
50 ms 4.752 s 4.812 s 12 ms

50 ms 周期看起来“更快”,并不是它更擅长检测掉落,而是故障开始时,它手里的观测已经旧了 48 ms,距离 60 ms 门槛只差 12 ms。

因此必须同时报告故障时刻、最后捕获时刻和报警时刻。只比较最后一列,会误把采样相位差异当成策略能力。

20 ms旧包重送条件:实际接触已经消失,收到的包仍报告正常,捕获年龄持续增加

图的接触和年龄曲线来自未加年龄检查的 elapsed40 回合;竖线标出配对 fresh60 回合的实际报警时刻。两者在决策分歧前的物理状态一致。阴影表示强制松爪区间,不表示传输故障在 5.04 秒结束。

年龄门槛也不能脱离正常周期选择。本例最大的正常更新间隔是 50 ms,60 ms 门槛只多留了 10 ms;真实系统若有传输和调度抖动,这个余量可能不够,本轮没有验证。

8. 如何确认不是程序自己给自己打分?

独立审计器不导入运行器或监测器。它从日志重建消息的来源、序号、捕获时刻、是否被采用、连续异常跨度和年龄,再计算报警与任务验收。

本轮实际通过:

  • 45 回合独立核验,没有 MuJoCo warning。
  • 45 对策略在首次决策分歧前,保存的物理状态前缀一致。
  • 9 条无扰动完整轨迹与上一轮成功基线完全一致。
  • 17 项新边界测试与原有 24 项测试通过。
  • 180 份已提交原始文件的字节哈希与审计记录一致。

所有无扰动回合完成放置;所有真实松爪条件都未完成放置。新鲜度检查改变的是“证据过期后还要不要继续”,没有把任务重新做成功。

这些是实现和记录的一致性检查,不等于真实机器人安全认证。

9. 自己复现,再问下一个问题

代码使用原工程的 Python 3.12、MuJoCo 3.3.7 和锁定依赖。协议先于运行提交,来源版本和环境写在 manifest.json,没有新增模型调用。

.venv/Scripts/python.exe -m unittest discover -s experiments/vl01_observation_freshness -p test_monitor.py -v
.venv/Scripts/python.exe experiments/vl01_observation_freshness/run.py --out outputs/my-freshness
.venv/Scripts/python.exe experiments/vl01_observation_freshness/audit.py outputs/my-freshness
.venv/Scripts/python.exe experiments/vl01_observation_freshness/render.py outputs/my-freshness

输出目录必须不存在。查看 代码与复现说明 和 逐回合审计。control.csv 保存每个控制步的判断,trajectory.csv 与 states.jsonl 保存 50 Hz 物理快照;后者不是完整的 500 Hz 状态序列。图表只读取实际记录,不重新执行物理,也不冒充场景录像。

本轮仍使用仿真真值和同一时钟。没有跨机器时钟同步、序号重启、伪造新时间戳、真实网络、视觉、自然摩擦滑移或恢复策略。监测仍只覆盖搬运阶段。

下一步应先区分“延迟但仍可用”和“必须重新观测”,再定义恢复条件。尤其不能在新消息刚恢复时,就自动接着执行旧动作。这个问题已写回原有 VL01 待办,尚未作为实验结论发布。

写给未来的自己合上文章前,留下一点自己的理解。

你的私人学习便签,只存在当前浏览器,不会上传或公开。清理浏览器数据会丢失,请导出留存。三种语言共用这篇文章的便签。

继续探索

继续探索

  1. 工程案例

    具身实践(三):抓住之后,还要一直确认吗?

    27回合MuJoCo对照:用单次观测缺失与受控松爪,检验持续监测、误停和40毫秒确认延迟,并公开代码、轨迹与重放。

    14 分钟已发布
  2. 工程案例

    具身实践(二):没抓住,就别继续搬运

    在 MuJoCo 中加入抬升与双侧接触确认,用18回合对照检验停止空手搬运,并公开轨迹、视频、代码与局限。

    12 分钟已发布
  3. 项目 / 003

    Hohoo 具身实验

    七轮MuJoCo教学对照:从拾取偏移到阶段契约,保留原始轨迹、决策与独立审计。

    实验中
  4. 实验 / 008

    E4 / 观测新鲜度与采样间隔

    区分消息到达与新证据,检验固定计数在不同采样周期下的含义。

    已完成

接下来,走哪条路?

依据文章关联与同主题已发布内容整理,不使用随机推荐。

← 全部文章