DEEP 95 · 第十辑 · duck-control 逐函数精读 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 95 · policy.rs(一):Net 与技能枚举

解读对象:duck-control/src/policy.rs(811 行)
需要的前置:解读 03(同文件的导览篇);解读 96是本篇的下半场

从这一篇起进入函数级精读。模块注释(policy.rs:1-14)先立两条规矩:走与站由速度指令的模长决定,技能网络(sit↔stand、ground pick、踢腿)由 robotd 的调度器显式点名,优先级规则住在那边;"一切都在加载时校验,而不是在推理时"(policy.rs:10-14)。本篇读前半:这个文件怎么给"该跑哪个网络"命名。

1. Net:一个五选一的枚举

Rust 的枚举(enum)是"几个变体里取一个"的类型,每个变体还可以携带数据。Net(policy.rs:204-219)正是这么用:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Net {
    Walk,
    Stand,
    SitStand,
    GroundPick,
    Skill(usize),
}

#[derive(...)] 让编译器自动生成比较、拷贝等样板实现;Copy 使 Net 像整数一样按值复制,不涉及所有权转移(所有权是 Rust 的内存规则:每个值有唯一主人)。四个具名变体各带一行注释:SitStand 复用 twist 的 vx 槽传姿势旗标(1=坐下,0=站立,policy.rs:208-209);GroundPick 的 twist 槽传 [cos φ, sin φ, 0] 相位(policy.rs:210-211)——观测布局不扩,技能只是"指令编码"。

最值得读的是 Skill(usize)(usize 是平台位宽的无符号整数,policy.rs:212-218)。注释交代了重构史:踢左、踢右、roulade 曾经是三个枚举变体,后来发现它们是"同一件事重复三遍"——零指令网络、固定窗口、显式请求,只有时长与调参不同,那是数据不是类型。换成索引后,"给机器人加一个技能"从"改枚举发版"变成"加一条配置项"(policy.rs:214-218)。

2. PolicyPaths:配置即能力

PolicyPaths(policy.rs:223-233)回答"有哪些文件可载":walk: PathBuf 必填;stand/sitstand/ground_pick 是 Option<PathBuf>——Option 是 Rust 表达"可能没有"的类型(Some(x) 有、None 无),这里语义精确:没配 = 机器人没这个能力,不是"加载失败"。skills: Vec<PathBuf>(Vec 是可变长数组)按调用方希望的优先序排列,注释补了一句:空列表是"没有特技的机器人",不是"缺了东西的机器人"(policy.rs:229-232)。

3. 能力查询:先问有没有,再决定用不用

Policy 结构体(policy.rs:240-251)按同样形状存网络,查询方法全走 Option:has_standing/has_sitstand/has_ground_pick(policy.rs:316-326)、skill_count()(policy.rs:329-331)——调度器的纪律是先用 has_* 问,再发 Net 请求。will_stand(policy.rs:310-314)是三条件与:stand.is_some() && !standing_disabled && twist_magnitude <= 0.05(阈值常量 DEFAULT_STANDING_THRESHOLD = 0.05,policy.rs:28)。它单独存在,是因为调用方要拿同一答案去选增益和动作缩放,"问两次不能得到两个答案"(policy.rs:306-309);set_standing_disabled(policy.rs:302-304)给滚轮和摔倒恢复用——它们占用了站立网络,模长规则必须失效(字段注释 policy.rs:248-250)。

infer(policy.rs:336)入口还有一道保险:请求了没加载的网络,回退到 Walk 而不是 panic——注释原话"错误的步态好过死掉的控制线程"(policy.rs:333-335)。但设计意图是这条回退永远走不到:它给竞态兜底,不给调用方偷懒。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页