4884fc34ae
补充上一个提交(CRLF 死脚本)的规则化:把"文件在、文档说能用、一跑就死" 和"bash -n 漏报 CRLF"两条症状写进第 8 节反例表,并新增 R6.6。 为什么单独立一条:这次踩的不是"哪个脚本写错了",而是"验收手段本身不可靠"—— bash -n 返回 OK 却执行立刻死。所以 R6.6 明确三件事: 验语法 bash -n(必要但不充分)、验能用 bash <脚本> --help、验行尾看 file/.gitattributes。 顺带在反例表补了"代码里一半英文 docstring 一半中文注释"一行,指向 R6.5。 Co-authored-by: Cursor <cursoragent@cursor.com>
452 lines
22 KiB
Markdown
452 lines
22 KiB
Markdown
# 框架规则(`docs/FRAMEWORK_RULES.md`)
|
||
|
||
> **这份文档给两类读者看:**
|
||
> - **改这个仓库的 AI**:开工前先读,逐条遵守。这些不是风格建议,是**硬规矩** ——
|
||
> 违反它们的改动会在真机上以"不报错但结果是错的"方式失败,而这正是本仓库
|
||
> 花最多代价在防的事。
|
||
> - **review 人**:每条例规都有编号(`R1`…),可以直接引用,例如
|
||
> "这条违反 R7.2"。
|
||
>
|
||
> 配套:`.cursor/rules/*.mdc` 是给 Cursor 的常驻规则(自动注入),内容是本文件的
|
||
> 压缩版;本文件是**唯一权威**,两者冲突时以本文件为准。
|
||
>
|
||
> 规则是可以改的 —— 但必须改这里,而不是在代码里偷偷绕开。改规则时写清**为什么**。
|
||
|
||
---
|
||
|
||
## 0. 三条元规则
|
||
|
||
其余所有规则都是从这三条推出来的。拿不准某件事该怎么做时,回到这三条。
|
||
|
||
### M1. 静默失效是头号敌人
|
||
|
||
本仓库遇到的大部分问题都不报错:**话题在、图像有、TF 查得到、日志干净,结果是错的。**
|
||
所以:
|
||
|
||
- 任何"检查 / 校验 / 验收"都必须**能真的失败**。写完先问一句:
|
||
"我故意把它弄坏,这条检查会红吗?" 不会红 = 这条检查等于没有(见 `R5.2`)。
|
||
- 宁可**当场报错**,也不要"宽容降级"。降级会把"没检查"伪装成"检查通过"。
|
||
真实案例:位姿解析函数格式对不上,于是永远返回空、调用方按设计"跳过位姿核对",
|
||
结果位姿检查从来没跑过,而日志只说"跳过"(见 `R5.4`)。
|
||
- 校验代码自己也会静默失效。凡是"检查 X 是否为空"的地方,都要问:
|
||
"它会不会被填成一个**假值**?"(真实案例:`sdformat` 用 `__default__` /
|
||
`__default_topic__` 填缺失字段,见 `R4.6`)
|
||
|
||
### M2. 单一数据源
|
||
|
||
同一个事实**只允许写一处**,其余全部派生。
|
||
|
||
- 能算出来的不要抄(内参由视场与图幅算、相机位姿从 world 文件解析)。
|
||
- 必须抄的(如仿真侧要提前知道像素门槛)**必须有人守着**:
|
||
用脚本交叉核对真机配置,不一致就判失败(`R2.6`)。
|
||
- 同一份事实出现两处时,一定有一处会先过期。过期的那处不会报错。
|
||
|
||
### M3. 口径必须显式
|
||
|
||
**单位、坐标系、符号、参考基准**必须写下来,并且**只在一个地方换算**。
|
||
|
||
- 毫米还是米?相对零还是相对基线?压下去为正还是为负?
|
||
这些都不写清就会"数值看起来合理、语义是错的"。
|
||
- 换算只做一次,放在适配层(相机适配器、触觉适配器),下游不许再猜。
|
||
|
||
---
|
||
|
||
## 1. 配置与数据
|
||
|
||
### R1.1 分层优先级固定:命令行 > `site.yaml` > 包内默认
|
||
|
||
`config/site.yaml` 是**站点数据**(SN/IP/相机序列号/锁高/权重路径),**不进 git**;
|
||
包内 `config/*.yaml` 是默认值,被 site 覆盖。
|
||
|
||
**禁止**在代码里硬编码站点值。真实反例:15 处硬编码的 `Rizon4-063989`
|
||
(见 `R8` 的清单),换一台臂就会静默连错机器。
|
||
|
||
### R1.2 新增 `site.yaml` 键必须二选一:**接线** 或 **明确告警**
|
||
|
||
声明了却没接线的键是最难查的一类问题:用户按文档改了 `site.yaml`,行为毫无变化,
|
||
也不报错。所以每个新键都要么真的接线,要么在启动时**告警**(`wired: false` +
|
||
`unwired_warnings()` 那套做法)。
|
||
|
||
> ⚠️ 现状:`wired:` 目前只是 `manifest.yaml` 里的**注释性约定**,
|
||
> `arm_interfaces.manifest` 并不解析它。所以"未接线"的保障实际来自
|
||
> `sim_site.unwired_warnings()` 这类**代码里的告警**,不是清单。
|
||
> 加键时不要指望清单替你守法。
|
||
|
||
### R1.3 设备一律走 `devices/<kind>/<brand>/`,并带 `manifest.yaml`
|
||
|
||
```
|
||
devices/<kind>/<brand>/
|
||
├── manifest.yaml # id/label/kind/vendor/version + assets + driver + capabilities + models + config_schema
|
||
├── driver/ # 与厂商 SDK 对接的实现(可替换)
|
||
└── models/<型号>/ # 该品牌的型号专属文件
|
||
```
|
||
|
||
- `kind` 用固定的那几个(`robots` / `grippers` / `cameras` / `sensors` / `sim`),
|
||
目录名要与 `arm_interfaces.KIND_FOLDERS` 一致。
|
||
- **换品牌 = 新增一个目录**,不是改现有目录。上层按 `manifest.yaml` 选型,
|
||
不认具体品牌名。
|
||
- `assets[].required: true` 的物料缺失时,框架会报错并给出**格式与命名要求**
|
||
(不只是"文件不存在")。
|
||
|
||
### R1.4 权重一律走 `models/`,规则在 `models/README.md`
|
||
|
||
- 二进制(`.pt/.onnx/.engine`)**不进 git**;`.labels.yaml`(文本标签)**要进**。
|
||
- 转换链:`models/staging/` 放待转换的原始文件 → `weights.sh convert` →
|
||
自动改名归位到 `models/<分组>/` 并固化标签。
|
||
- `models/**/best.pt` 这类**悬空引用**(配置指向不存在的文件)必须修掉或改成实际存在的,
|
||
查法:`weights.sh doctor`。
|
||
|
||
### R1.5 什么入库、什么不入库
|
||
|
||
| 入库 | 不入库 | 理由 |
|
||
|------|--------|------|
|
||
| `src/` `scripts/` `docs/` `devices/` `config/site.example.yaml` | `config/site.yaml` | 站点数据含本机 IP/序列号 |
|
||
| `models/README.md`、`models/**/*.labels.yaml` | `models/**/*.{pt,onnx,engine}` 等 | 二进制体积大、每台机器重转 |
|
||
| `handeye/poses/` `handeye/results/` | `handeye/tmp/` | 示教点与标定结果是用真机时间换来的**资产**,要能回滚 |
|
||
|
||
`config/site.yaml` 改了以后**必须**重跑 `python3 scripts/gen_site_example.py`,
|
||
否则模板会漂移(有一条 parity 测试会红)。
|
||
|
||
---
|
||
|
||
## 2. 几何、单位与坐标系
|
||
|
||
### R2.1 单位一律米与弧度
|
||
|
||
SDF / URDF / 几何计算里不允许出现毫米。用毫米建模的 mesh 必须先缩放。
|
||
|
||
**例外且必须显式**:真机 RealSense 的深度图是 `16UC1` **毫米**(见 `R2.3`)。
|
||
|
||
### R2.2 相机的 `frame_id` 必须是**光学系**
|
||
|
||
ROS 的 `image` / `camera_info` 必须用光学约定(Z 朝前 / X 朝右 / Y 朝下,REP-103/105),
|
||
不能用相机本体系(X 朝前 / Y 朝左 / Z 朝上)。两者差 `rpy=(-90°, 0, -90°)`。
|
||
|
||
盖错 frame 的后果:`tf2` 查询**能查到、不报错**,但反投影出的三维点整体转错,
|
||
而单目预览完全看不出来。
|
||
|
||
### R2.3 深度:真机是毫米,Gazebo 是米 —— 必须真换算
|
||
|
||
| 来源 | 编码 | 单位 |
|
||
|------|------|------|
|
||
| 真机 RealSense aligned_depth | `16UC1` | 毫米,`0` = 无读数 |
|
||
| Gazebo 深度相机 | `32FC1` | 米 |
|
||
|
||
直接改个名字转发,应用侧按毫米解释会整体差 1000 倍。换算只做一次,
|
||
放在 `sim_camera_adapter`。
|
||
|
||
### R2.4 `rpy` 的复合顺序是 `Rz·Ry·Rx`
|
||
|
||
不是 `Ry·Rz·Rx`。单轴旋转看不出来,多轴组合才暴露。
|
||
|
||
**URDF 相机的视线是本体 X 轴**("X 朝前"约定)。所以"先俯 50° 再偏航 180°"
|
||
会把视线翻到**背面**。D435 就这么翻过:背对台面只看得见地面,什么都不报,
|
||
**而且几何自洽**(点确实落在真实地面上)。
|
||
|
||
### R2.5 Gazebo 发的 `camera_info` 不可信
|
||
|
||
Fortress 恒发 `fx=fy=277, cx=160, cy=120`(它的默认 60° 视场 + 半分辨率主点),
|
||
与真正渲染用的视场无关。内参必须由 `cameras.yaml` 的 `horizontal_fov` 与图幅**算出**,
|
||
并覆盖 gz 发的那份。
|
||
|
||
### R2.6 像素门槛与**图幅**绑定
|
||
|
||
`hole_detect.yaml` 的门槛(`hough_min_radius`、`hole_distance_min`)是**像素**量,
|
||
只在特定分辨率下有意义。改图幅要同时改三处(`cameras.yaml`、SDF、
|
||
必要时重算真机门槛),启动时会比对并告警。
|
||
|
||
### R2.7 位姿检查要有容差,且容差要说明理由
|
||
|
||
物体的落座微动是正常的。卡到 `1e-6` 只会得到永远为真的告警,人就会把检查关掉。
|
||
默认 5mm 起步(见 `sim_scene.pose_mismatches`)。
|
||
|
||
---
|
||
|
||
## 3. 触觉与力
|
||
|
||
### R3.1 力的语义是"相对静止基线的增量"
|
||
|
||
真机 Orisys 输出的是形变任意单位(非牛顿),且基线会漂移,所以全链路
|
||
(含下游阈值)都以**相对基线的增量**为语义。仿真必须做同样的事:
|
||
不减基线,下游阈值就对不上。
|
||
|
||
基线要慢跟踪(只在确认无接触时更新),否则换姿态后的残余会被当成接触。
|
||
|
||
### R3.2 `fnormal` 压下去为正,且 clamp 到非负
|
||
|
||
两指符号一致。这条是**实测**结论(`config/tactile.yaml` 里有实测记录),
|
||
不是从 URDF 推导出来的。
|
||
|
||
### R3.3 轴向映射必须实测,不许推导
|
||
|
||
哪个轴是"指腹法向"要用实验确定(夹一块大平面薄板等稳态)。猜错不会报错,
|
||
只会让所有力的语义静默错位。实测方法与结果记在 `config/tactile.yaml`。
|
||
|
||
用小方块做这个实验是错的:点/边接触会让法向偏、摩擦混进来。
|
||
|
||
### R3.4 接触布尔必须用**超时**判据
|
||
|
||
Gazebo 的接触消息是**事件式**的:接触时发,离开时**一条都不发**。
|
||
写成"收到=接触 / 没收到=不管",离开工件后 `contact` 会永远停在 `True`。
|
||
|
||
判据 = "最近 `contact_timeout_s` 内收到过消息"。
|
||
|
||
### R3.5 仿真与真机的力的量级不同,`force_scale` 是必须确认的口径
|
||
|
||
仿真输出 = Gazebo 牛顿 × `force_scale`。`force_scale = 1.0` 时输出牛顿,
|
||
**不能**直接套用真机那套阈值(250/600)。位置控制器是"刚性地压进去"的,
|
||
实测能到几百。
|
||
|
||
选错口径不报任何错:阈值只是"不触发"或"一碰就炸",看起来像算法问题。
|
||
|
||
### R3.6 视触觉的**图像**在仿真里只能是占位
|
||
|
||
仿真没有弹性体成像,深度图/力矢量图只能是空/NaN。话题与消息结构要保留
|
||
(下游不用改代码),但要打一次明确日志说明它不是真实数据。
|
||
|
||
---
|
||
|
||
## 4. 仿真(Gazebo)
|
||
|
||
### R4.1 仿真与真机**共用同一套 `site.yaml` 参数**
|
||
|
||
任何"仿真专用参数"都是未来"仿真里调好的值搬到真机不生效"的隐患。
|
||
只允许**物理引擎自身的**参数(重力、步长)放在 `sim.*` 下。
|
||
|
||
### R4.2 `world` 里声明任何一个 `<plugin>` 都会挤掉默认系统
|
||
|
||
声明了任何插件后,Gazebo **不再**加载默认的 Physics / UserCommands / SceneBroadcaster。
|
||
|
||
少了 UserCommands 的后果最阴:`/world/<名>/create` 服务不存在 → 模型根本没进 Gazebo,
|
||
而 `create` **仍然打印** `OK creation of entity`。
|
||
|
||
所以这些系统必须在 world 里**显式声明**(含 `contact-system`、`forcetorque-system`)。
|
||
|
||
### R4.3 插件与资源的搜索路径必须在 `ros2 launch` **之前**设好
|
||
|
||
conda 激活不会往 `LD_LIBRARY_PATH` 加东西,而 gz 的插件搜索路径需要
|
||
`$CONDA_PREFIX/lib`。症状是 `couldn't find shared library` 而库其实就在那儿。
|
||
|
||
**不要绕过 `scripts/sim_env.sh` 直接 `ros2 launch`。**
|
||
|
||
### R4.4 上游描述(`flexiv_description` 等)**不许直接改**
|
||
|
||
第三方包一律在**启动时归一化**,不在上游文件里下手:
|
||
|
||
- 缺 `<inertial>` / 零质量 link → NaN 惯性、物体消失
|
||
- `<mimic>` 标签被 `sdformat` **丢弃** → 夹爪"控制器 active 但手指不动"
|
||
- 无质量的根 link(world link)会**连整棵子树被静默丢掉**
|
||
|
||
改上游还有个额外代价:下次 `fetch_flexiv.sh` 会把你的改动覆盖掉。
|
||
|
||
### R4.5 场景物体走 `scene.yaml`,不进 world
|
||
|
||
`world` 是"环境"(地面/台面/相机/系统插件),`scene.yaml` 是"这一批活做什么"。
|
||
动 world 的风险面太大(`R4.2`)。
|
||
|
||
模型约定:**原点在底面中心**、单位米、目录名 = `<model name>` = `model.config`
|
||
里的 `<name>` = 推理类别名。
|
||
|
||
### R4.6 模型/场景校验必须逐**碰撞体**、三维判
|
||
|
||
- 不能拿总包围盒判重叠:正常装配里本来就有嵌套(规立在规座方孔里),
|
||
粗判会把正常嵌套报成碰撞,逼着人把检查关掉。
|
||
- 恰好贴合不算重叠(规的台肩正好坐在规座顶面上)。
|
||
- 变换必须用"中心 + 半宽":对 min/max 取绝对值再对称化会把偏移量吃掉、
|
||
盒子凭空变大。
|
||
- 校验要用**从 SDF 解析出来的**几何,不能在 `scene.yaml` 里再抄一份尺寸。
|
||
|
||
### R4.7 建模:碰撞体用简单几何,视觉可以加"只给算法看"的细节
|
||
|
||
`<collision>` 决定物理,用 box/cylinder;`<visual>` 决定相机看见什么,
|
||
可以加亮环、色块这类只服务于检测的东西。
|
||
|
||
工件的碰撞体是方孔、视觉是圆环,**这是刻意的**:真机的 HoughCircles 靠亮环找孔,
|
||
只按碰撞体建视觉会让孔检测在仿真里永远失败,而那看起来像算法问题。
|
||
|
||
> 由此产生的约定:`visual` 的**名字有意义**(`insert_*_ring` / `center_bore`),
|
||
> `sim_scene.features_from_workpiece` 按名字前缀读特征。改名字要同步改那个函数。
|
||
|
||
### R4.8 夹爪/被夹物体的接触刚度要按"位置控制"的实际行为调
|
||
|
||
位置控制器会"刚性地压进去",默认刚性接触会把物体弹飞。需要放软时要**写明理由**
|
||
(照抄现成参数前先想清楚你的物体是不是也要被夹)。
|
||
|
||
### R4.9 不用"有图像 / 有话题 / TF 查得到"验收
|
||
|
||
这些只能证明"链路通"。验收必须是对**物理量**的断言:
|
||
|
||
| 工具 | 判据 |
|
||
|------|------|
|
||
| `scripts/check_sim_cameras.py` | 深度反投影必须命中已知平面(地面 z=0 / 台面 z=0.40) |
|
||
| `scripts/check_sim_scene.py` | 摆放自洽 + 像素门槛与真机一致 + 成像几何达标 + 手外相机**完整**看得见工件 |
|
||
|
||
### R4.10 停仿真用 `scripts/stop_sim_gz.sh`,不要 `pkill -f`
|
||
|
||
`pkill -f` 会先匹配到**调用它的那个 shell**(它的命令行里含同样的字符串),
|
||
表现是"命令秒返回、什么都没停"。脚本按进程树精确识别,并在杀完之后**复查残留**。
|
||
|
||
---
|
||
|
||
## 5. 代码与测试
|
||
|
||
### R5.1 纯逻辑要抽成纯函数,才可能被钉住
|
||
|
||
把"数据变换"从 ROS 节点里抽出来(`sim_cameras`、`sim_tactile`、`sim_scene`),
|
||
因为静默失效点的错误**不会在运行时暴露**:轴向映射反了照样"有力、会响应、
|
||
松开归零"。抽出来才有单测可写。
|
||
|
||
### R5.2 每条"检查/校验"都要有能证明它有效的测试
|
||
|
||
尤其是**负向**测试:故意造出坏数据,断言检查会报错。
|
||
只测"好数据通过"的测试,无法区分"检查有效"和"检查恒真"。
|
||
|
||
### R5.3 不许把"降级跳过"写成"通过"
|
||
|
||
调用方必须把降级当作**告警**打印出来并说明原因。真实反例见 `R5.4`。
|
||
|
||
### R5.4 解析外部输出:先确认真实格式,再写解析器
|
||
|
||
外部工具(`ign`、`sdformat`、厂商 SDK)的文本输出**没有稳定契约**,
|
||
而且格式与直觉不一致是常态。
|
||
|
||
先用真实输出跑一遍看清楚了再写解析;解析失败要返回"未知"而不是"零"。
|
||
真实反例:按 `x: N` 键值对写解析器,而 Fortress 实际输出是方括号两行,
|
||
于是位姿检查从来没跑过。
|
||
|
||
### R5.5 删除 / 重构前先做全仓库引用扫描
|
||
|
||
不靠印象。反例:一个 `assets_root()` 函数指向从未存在的 `assets/` 目录、
|
||
零调用者,长期没人发现(见 `R8`)。
|
||
|
||
### R5.6 不要在中文路径下 `colcon build`
|
||
|
||
ROS 构建对非 ASCII 路径会出各种问题。工作目录保持 ASCII(推荐 `~/ros2`)。
|
||
|
||
### R5.7 真机与仿真的构建产物必须隔离
|
||
|
||
`build/ install/ log/` 是真机;`build_sim/ install_sim/ log_sim/` 是仿真
|
||
(`scripts/build_sim.sh`)。两套并存是刻意的:同一份源码,两个环境的依赖路径不同,
|
||
共用 `install/` 会互相覆盖。
|
||
|
||
---
|
||
|
||
## 6. 文档与提交
|
||
|
||
### R6.1 文档与实现不一致时:以实现为准,**立刻**修文档
|
||
|
||
文档里"已就绪 / 未接线"必须**如实**标注。看到 ✘ 不要凭印象改成 ✔。
|
||
|
||
### R6.2 每份文档要有"读法"和"这份文档记录什么事实"的说明
|
||
|
||
本仓库的文档面向"明天就要动手的人",所以要写清:先读哪节、哪些是硬事实、
|
||
哪些是踩过的坑、怎么验证。
|
||
|
||
### R6.3 结论性数字必须能复现
|
||
|
||
写进文档的性能/几何数字(如"台面命中 11.9%")要么给出命令,要么给出脚本名。
|
||
|
||
### R6.4 提交信息:中文,讲**为什么**,附实测验收数据
|
||
|
||
格式:`<阶段号> <一句话结果>` + 正文说明"问题是什么、为什么这么改、怎么验证的"。
|
||
修静默失效时,把"症状"写进去 —— 那是后来人搜索时唯一能匹配上的东西。
|
||
|
||
### R6.5 注释、docstring、提示信息一律用中文
|
||
|
||
现场操作的人读中文。所以本仓库(`src/arm_*/**`、`scripts/**`)里:
|
||
|
||
- **注释、docstring、`print`/日志/异常里给人看的句子 = 中文**
|
||
- **标识符、API 名、话题名、参数名、字段名、文件名、命令名 = 保留英文原文**,
|
||
写在中文句子里时用双反引号包起来,例如 ``SITE_ROS2_ROOT``、``devices/<kind>/<brand>/``
|
||
- 单位、十六进制、协议报文、CRC 值、寄存器地址、命令示例**原样保留**
|
||
|
||
**例外:`src/flexiv_*/` 是厂商上游代码,不许改、也不许翻译**(见 R4.4)。
|
||
|
||
只改文本的提交,必须能自证"没碰到逻辑"。做法:把新旧两份源码都解析成 AST,
|
||
删掉 docstring、把字符串常量抹平、把 f-string 只留"插值了几次 + 插值是什么",
|
||
再比对 —— 必须完全相同。这条检查会抓出"顺手改名""顺手调整语句顺序""漏掉一个
|
||
f-string 插值"这类问题;而"为了中文语序把两个插值换个显示顺序"属于允许的改动。
|
||
|
||
工具:`python3 scripts/check_text_only.py`(基准是 `git HEAD`,所以要在提交前跑)。
|
||
|
||
### R6.6 行尾一律 LF;验脚本要**真跑**,不能只看 `bash -n`
|
||
|
||
行尾由 `.gitattributes` 兜住(`* text=auto eol=lf`,厂商上游 `-text` 不动)。
|
||
|
||
为什么专门立一条:`scripts/run_hole_detect.sh` 和 `scripts/run_dual_gripper.sh`
|
||
曾经是 CRLF,于是 bash 把行尾的 `\r` 当成标识符的一部分,
|
||
`case "$1" in\r`、`set -eo pipefail\r` 全部变成非法记号 ——
|
||
**文件在、五处文档说能用、命令一敲就死**,而报错信息里的 `\r` 根本看不出来。
|
||
|
||
更坑的是:`bash -n` 对其中一个还返回 OK,实际执行立刻死在第 6 行。
|
||
所以"静态检查通过"不等于"脚本能用":
|
||
|
||
- 验语法:`bash -n`(必要但不充分,CRLF 会漏报)
|
||
- 验能用:`bash <脚本> --help`
|
||
- 验行尾:`file <文件>`,或看 `.gitattributes` 有没有覆盖到
|
||
|
||
---
|
||
|
||
## 7. 改动前的检查清单
|
||
|
||
改任何东西之前/之后,逐条过一遍:
|
||
|
||
| # | 检查 | 对应规则 |
|
||
|---|------|----------|
|
||
| 1 | 我要改的这个事实,仓库里有几处在写?其余的是派生的吗? | M2 |
|
||
| 2 | 我加/改的配置键,真的接线了吗?没接线会告警吗? | R1.2 |
|
||
| 3 | 涉及单位/坐标系/符号吗?换算只做了一次吗?写下来了吗? | M3、R2 |
|
||
| 4 | 我加的检查,故意弄坏数据它会红吗?有负向测试吗? | M1、R5.2 |
|
||
| 5 | 解析外部输出前,我看过真实输出了吗? | R5.4 |
|
||
| 6 | 我改的是上游第三方文件吗?(应该走归一化/适配层) | R4.4 |
|
||
| 7 | 改完跑了验收工具吗(`check_sim_cameras` / `check_sim_scene`)? | R4.9 |
|
||
| 8 | 文档里的状态标注与实现还一致吗? | R6.1 |
|
||
| 9 | 提交信息里有"为什么"和实测数据吗? | R6.4 |
|
||
|
||
---
|
||
|
||
## 8. 历史反例清单(别再犯)
|
||
|
||
按"症状"排序 —— 因为出问题时你是先看到症状,再回来查这张表。
|
||
|
||
| 症状 | 真实原因 | 规则 |
|
||
|------|----------|------|
|
||
| 三维点整体偏半米,但单目预览正常 | 用了 gz 发的 `camera_info`(恒为 60° 视场的默认值) | R2.5 |
|
||
| 深度差 1000 倍 / `frombuffer` 读出垃圾 | 把 gz 的 `32FC1` 米直接当毫米用 | R2.3 |
|
||
| 反投影的点转到别处,`tf2` 却能查到 | `frame_id` 盖成了相机本体系 | R2.2 |
|
||
| 相机背对目标,只看得见地面 | `rpy` 复合顺序理解错 / 视线是本体 X 轴 | R2.4 |
|
||
| 模型"根本没进 Gazebo",但 `create` 打印成功 | world 里声明了插件,挤掉了 UserCommands | R4.2 |
|
||
| `gz_ros2_control` 报找不到共享库,但 `ldd` 全齐 | 插件搜索路径没含 `$CONDA_PREFIX/lib` | R4.3 |
|
||
| 物体一 spawn 就消失 | 缺 `<inertial>` / 零质量 link → 惯性 NaN | R4.4 |
|
||
| 夹爪控制器 active 但手指不动 | `<mimic>` 被 `sdformat` 丢弃 | R4.4 |
|
||
| 世界相机与它的话题一起消失 | 挂在无质量的 world link 上,整棵子树被丢 | R4.4 |
|
||
| `contact` 离开工件后永远是 `True` | 接触消息是事件式的,没有"离开"事件 | R3.4 |
|
||
| 力的语义全错,但"有力、会响应、松开归零" | 轴向映射猜的,没实测 | R3.3 |
|
||
| 明明夹住了,`fnormal` 恒为 0 | 基线把真实接触吃掉了(基线更新条件写错) | R3.1 |
|
||
| 阈值永不触发或一碰就炸 | 仿真牛顿与真机任意单位混用(`force_scale` 没标定) | R3.5 |
|
||
| 位姿检查从来没跑过,日志只说"跳过" | 解析器格式对不上,降级把"没检查"伪装成通过 | M1、R5.4 |
|
||
| 校验代码永远通过 | `sdformat` 用 `__default__` / `__default_topic__` 填了缺失字段 | M1 |
|
||
| 报出根本不存在的碰撞 | 包围盒变换对 min/max 取了绝对值、把偏移吃掉 | R4.6 |
|
||
| 正常嵌套被判成重叠,只好把检查关掉 | 拿总包围盒判重叠,没逐碰撞体三维判 | R4.6 |
|
||
| 孔检测在仿真里根本不可能通过 | 仿真分辨率与真机不同,像素门槛失效 | R2.6 |
|
||
| 仿真"通过"、真机不认 | 像素门槛抄了两份,漂移了 | M2、R2.6 |
|
||
| 脚本说"已停止",进程还在跑 | 进程模式表漏项(漏过 `robot_state_publisher`) | M1、R4.10 |
|
||
| `pkill -f gz` 秒返回、什么都没停 | 它先匹配到调用它的 shell | R4.10 |
|
||
| 换了台臂却连错机器 | 硬编码的 `Rizon4-063989` 默认值 | R1.1 |
|
||
| `assets_root()` 指向从未存在的 `assets/` | 零调用者的死代码,没人发现 | R5.5 |
|
||
| 脚本报 `$'\r': 未找到命令`、`set: pipefail: 无效的选项名` | 脚本是 CRLF 行尾,`\r` 被当成标识符的一部分 | R6.6 |
|
||
| `bash -n` 说没问题,真跑立刻死 | `bash -n` 对 CRLF 会漏报 —— 静态检查≠能用 | R6.6 |
|
||
| 代码里一半英文 docstring 一半中文注释 | 没有统一口径,谁写谁定 | R6.5 |
|
||
|
||
---
|
||
|
||
## 9. 这份文档怎么用(给 AI 的具体指令)
|
||
|
||
改这个仓库时:
|
||
|
||
1. 先读第 0 节,记住三条元规则。
|
||
2. 按第 7 节的清单逐条过。
|
||
3. 出问题时先查第 8 节(按症状查)。
|
||
4. 要在代码里"绕开"某条规则时,**先改这份文档**并写清理由 ——
|
||
绕开而不改文档,下一个人会按文档改回去。
|