# open_drybox 子目录 — Claude 编码指引

本文件是 `source/mindbot/mindbot/tasks/manager_based/il/open_drybox/` 的局部 Claude 指引，
补充根目录 `CLAUDE.md` 中未覆盖的细节。

---

## 文件职责速查

| 文件 | 职责 | 修改频率 |
|---|---|---|
| `mindrobot_cfg.py` | 机器人物理参数、初始位姿、关节名称常量 | 低（调参时改） |
| `mindrobot_left_arm_ik_env_cfg.py` | 单臂 IK 环境（Rel/Abs）配置 | 中 |
| `mindrobot_dual_arm_ik_env_cfg.py` | 双臂 IK 环境配置，**直接继承** `ManagerBasedRLEnvCfg` | 中 |
| `mdp/__init__.py` | 聚合 MDP 函数，**import 顺序决定覆盖优先级**（后者覆盖前者） | 低 |
| `mdp/observations.py` | 自定义观测函数，覆盖 IsaacLab 同名函数 | 低 |
| `__init__.py` | Gym 注册，新增任务必须在这里 `gym.register` | 加任务时改 |

---

## 关键设计决策

### 1. 不修改 IsaacLab 源码
所有自定义函数放在本地 `mdp/` 中，通过 import 覆盖机制生效：
```python
# mdp/__init__.py 的 import 顺序
from isaaclab.envs.mdp import *             # 基础函数
from isaaclab_tasks...stack.mdp import *    # stack 任务函数（含 ee_frame_pos 等）
from .observations import *                 # 本地覆盖（gripper_pos 等）
```

### 2. 双臂 env cfg 独立，不继承单臂
`mindrobot_dual_arm_ik_env_cfg.py` 直接继承 `ManagerBasedRLEnvCfg`，
避免单臂配置的耦合问题。

### 3. gripper_pos 必须指定 joint_names
```python
# 正确用法
ObsTerm(func=mdp.gripper_pos, params={"joint_names": MINDBOT_LEFT_GRIPPER_JOINTS})
# 错误：不传 joint_names 会抛 ValueError
ObsTerm(func=mdp.gripper_pos)
```

### 4. XR 双臂共享 XrClient
两个 `XrTeleopController` 实例必须共享同一个 `XrClient`，否则 SDK 崩溃：
```python
shared_client = XrClient()
teleop_left  = XrTeleopController(arm="left",  xr_client=shared_client)
teleop_right = XrTeleopController(arm="right", xr_client=shared_client)
```

### 5. 关节锁定（grip 松开时）
teleop 主循环缓存 root-frame IK 命令（`last_root_left/right`），
grip 未按下时发送冻结的缓存值，防止底盘漂移带动关节运动。

---

## 修改检查清单

**新增关节组或重命名关节**
- [ ] 更新 `mindrobot_cfg.py` 中的常量（`MINDBOT_*`）
- [ ] 同步更新 `mindrobot_dual_arm_ik_env_cfg.py` 的 Actions/Observations

**新增观测函数**
- [ ] 在 `mdp/observations.py` 中添加
- [ ] 确认 `mdp/__init__.py` 的 import 顺序不会被覆盖
- [ ] 在 env cfg 的 `PolicyCfg` 中添加 `ObsTerm`

**新增任务**
- [ ] 在 `__init__.py` 中 `gym.register`
- [ ] 确认 `mindbot/tasks/manager_based/__init__.py` 已 import 本包

**修改初始位姿**
- 注意避免奇异点：肘关节 `q3≠0`，腕关节 `q5≠0`
- 修改后在仿真中目视验证

---

## 常见错误

| 错误 | 原因 | 修复 |
|---|---|---|
| `AttributeError: module 'mdp' has no attribute 'ee_frame_pos'` | `mdp/__init__.py` 缺少 `stack.mdp` import | 确认三行 import 都在 |
| `KeyError: 'arm_action'` in teleop | 双臂任务动作名是 `left_arm_action`，不是 `arm_action` | 检查 `clear_ik_target_state` 的 is_dual_arm 分支 |
| `terminate called without an active exception` (XR SDK) | 两个 `XrClient()` 实例并存 | 共享 `shared_client` 传入两个控制器 |
| gripper obs assertion error | `gripper_pos` 未传 `joint_names` | 加 `params={"joint_names": ...}` |
