解读 28 · sitstand 配置:坐下与站立的对称性
解读对象:
microduck-replica/upstream/microduck_rl/src/mjlab_microduck/tasks/microduck_sitstand_env_cfg.py(929 行)
需要的前置:解读 27(standup 的单方向课程)、总纲
在做什么
27 篇的 standup 只会站起来;这个文件把坐与站合成一个策略、两个方向:命令槽 twist 的第一维被征用为姿势标志——cmd = [sit_flag, 0, 0],0=站、1=坐,全零即部署时的怠速态(microduck_sitstand_env_cfg.py:1-8)。命令由 SitStandCommandCfg(:576)以停留时间 POSTURE_DWELL_S = (3.5, 6.5) 秒(:87)为周期重采样翻转,12 秒的一集(EPISODE_LENGTH_S = 12.0,:83)至少含一次完整「坐→歇→起→歇」循环。reset 状态(50% 站 / 50% 坐)与命令独立,四个组合(坐下、站起、保持坐、保持站)都被等概率训练(:619)。
真实符号
对称性体现在每个任务 reward 都是 posture_* 家族:它们读当前命令,逐环境选择目标——坐是 SIT_Z = 0.060 + SITTING_TARGET_OVERRIDES(膝 ±1.35、髋俯仰 ∓0.4079,:101),站是 STAND_Z = 0.115 + HOME。于是 posture_height_gaussian(std 0.04 宽层 :285 / std 0.015 尖层 :295)、posture_height_l1(权重 6.0,取 sit 的 5.0 与 standup 的 7.5 之间,:308)在两个方向上共用同一套形状。
防摔的对称同样成对:MAX_DESCENT_SPEED = 0.05 与 MAX_RISE_SPEED = 0.08(:144-145),下降封顶 descent_speed 从 0 步就是正权重 10(函数自返回负值,:352),上升封顶 rise_speed 权重 0 起步、1500 迭代才 ramp 到 5(:862)——又是 27 篇那条「发现期不上税」的铁律,注释还记录了推后的原因:起身需要几步 vz > 0.08 的蹬地爆发,过早封顶会把半成品的起身卡死在头朝前的折叠姿态里(:855-861)。
最巧妙的机制是 POSTURE_RAMP_S = 2.0(:137):命令项内部把目标在坐/站之间以 2 秒斜坡滑动,posture 奖励追踪的是移动目标。这是防摔的主装置——二值目标下,提前到站可以白吃每步约 7 的目标奖励,摔坐一次只亏约 50 的有限代价,「摔」比「稳」赚 7 倍;有了斜坡,冲到目标前面意味着斜坡剩余时间里高度/合成分全为零,跟踪慢设定点才是 argmax(:128-137)。
设计取舍
另一个隐蔽工程点在物理层:全身碰撞模型的坐姿让躯干、折叠的腿和头同时贴地,默认 nconmax=35、solver 10 次迭代会在坐下的瞬间撑爆接触求解器产生 NaN,而 nan_state 终止等于惩罚「坐下」这个动作本身——策略刚学会又忘掉。修复是 cfg.sim.nconmax = 200、iterations = 30、ls_iterations = 50(:643-645)。这是「训练信号被物理引擎 bug 污染」的典型案例:先确认 NaN 不是任务失败,再去调 reward 就是白费劲。头撞地在这里不罚(sit 环境罚):过渡动作用头当第三支点被显式允许,靠 posture_composite 的 head_std = 0.40 因子(:434)只禁止「歇着时头耷拉到地」。
你带走的收获
- 互逆动作可以共享一套参数化 reward:把「目标」变成读命令的函数,一个策略学双向。
- 二值命令 + 密集目标奖励会鼓励冲撞;让目标内部斜坡滑动,把「提前到站」的收益清零。
- 接触求解器容量不足会伪装成任务失败(NaN 终止),重任务先查物理引擎日志。
- 惩罚函数自返回负值时权重写正——
run 7ev90yd9的双负号把三个罚项变成了暴力奖励(:344-351)。