216 lines
14 KiB
Markdown
216 lines
14 KiB
Markdown
# 协议规则表、字段表与状态机(P0 交付)
|
||
|
||
依据:`issue/附件/附件1.docx`《模拟器使用说明》、`issue/附件/附件2.docx`《模拟器通信接口说明及编程指南》。
|
||
本文是人类可读版本;**可执行版本**是 `src/mcm26b/protocol/rules.py`(字段表与错误语义)、
|
||
`src/mcm26b/protocol/timing.py`(计时模型)与 `src/mcm26b/config.py`(全部常量),
|
||
三者由 `tests/test_protocol_timing.py`、`tests/test_protocol_client.py` 固化验收。
|
||
|
||
## 1. 动作与耗时规则表
|
||
|
||
| 动作 | 指令 | 虚拟耗时 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 开始 | `POST /enter` | 0 | 初始位置 (0,0),初始频道 1;虚拟/程序时钟开始 |
|
||
| 检测 | `POST /measure` | 5 + 移动 + 换频道 | 无论结果(no_signal/near/direction)均为 5s |
|
||
| 清除 | `POST /clear` | 未发现 3 / 成功 5 + 移动 | 5 = 光学定位 3 + 激光清除 2;**不切换测向频道** |
|
||
| 结束 | `POST /exit` | 0 | 成功响应中 exit_reason 固定 `user_exit` |
|
||
| 切换频道 | 无独立指令 | 1 | 只由合法 `/measure` 的 channel 变化推断 |
|
||
| 移动 | 无独立指令 | 直线距离/5(m/s) | 由 `/measure` 或 `/clear` 的 position 推断 |
|
||
|
||
派生事实(易错点):
|
||
- 一次合法 `/measure` 完成后,测向机频道更新为本次检测频道;
|
||
- `/clear` 的 channel 只是目标频道,既不切频道、也不产生切换耗时,因此
|
||
"清除后原地再测"的耗时只取决于上一次 **/measure** 的频道;
|
||
- 只有 accepted=true 的请求推进虚拟时钟;`accepted=false` 的 virtual_time_s=0 不是当前时刻;
|
||
- 5 秒检测只是虚拟耗时,不要求现实等待。
|
||
|
||
## 2. 字段表
|
||
|
||
公共请求字段(4 条指令都必填):
|
||
|
||
| 字段 | 类型 | 约束 |
|
||
| --- | --- | --- |
|
||
| arena_id | string | 必须为 `"default"` |
|
||
| robot_id | string | 逐字节等于登录参赛队号;UTF-8 长度 1..64;无控制字符 |
|
||
| request_id | string | 会话内幂等键;UTF-8 长度 1..128;无控制字符 |
|
||
|
||
动作字段(实现见 `rules.ACTION_FIELDS`):
|
||
|
||
| 指令 | 字段 | 约束 |
|
||
| --- | --- | --- |
|
||
| /enter | (无附加) | |
|
||
| /measure | position{x,y}, channel | x/y 有限数值且 \|v\|<=2,000,000;channel 整数 1..20(1.0 亦接受,1.5 拒绝) |
|
||
| /clear | position{x,y}, channel | 同上;channel 是目标频道 |
|
||
| /exit | (无附加) | |
|
||
|
||
响应字段:
|
||
|
||
| 场景 | 字段 |
|
||
| --- | --- |
|
||
| 所有响应 | accepted, real_timestamp_ms, virtual_time_s |
|
||
| accepted=false | 仅上述 3 个字段(virtual_time_s=0) |
|
||
| /enter 成功 | + max_virtual_duration_s, max_real_duration_s, remaining_real_duration_s |
|
||
| /measure 成功 | + measure_result ∈ {no_signal, near, direction};direction 时另有 svd_deg(保留两位小数) |
|
||
| /clear 成功 | + clear_result ∈ {success, no_target_in_range} |
|
||
| /exit 成功 | + exit_reason = "user_exit" |
|
||
|
||
错误语义(必须同时检查 HTTP 状态与 accepted):
|
||
|
||
| 情形 | 结果 | 对 request_id 的影响 |
|
||
| --- | --- | --- |
|
||
| JSON 语法/重复键/缺字段/类型/坐标频道范围 | HTTP 400 | 不占用,修正后可复用 |
|
||
| 未知字段(含 position 下)、arena/robot 不匹配、测试状态拒绝 | HTTP 200, accepted=false | 不占用 |
|
||
| 动作被接受 | HTTP 200, accepted=true | 占用 |
|
||
| 同 ID 不同内容,或并发不同动作 | HTTP 409 | — |
|
||
| 网络失败重试同一动作 | 复用原 ID+原内容,服务器回放首次响应 | 不重复生效 |
|
||
| 429/500 | 可原样重试 | — |
|
||
| 404/405/413/415 | 确定性错误,不得重试 | — |
|
||
|
||
物理规则速查:Ω 半径 1800m;源数 10–16;频道 1–20;接收半径 [1000,1500]m(逐源未知);
|
||
near 阈值 5m;清除半径 20m(与定向朝向无关);定向覆盖 = 朝向两侧各 90°(含边界);
|
||
示向度误差幅度 ±1°,保留两位小数;虚拟限时 360000s;程序限时 1200s(以 /enter 的
|
||
remaining_real_duration_s 为准,不得假定恒为 1200);测试窗口 25 分钟。
|
||
|
||
## 3. 状态机
|
||
|
||
机器狗会话状态(`protocol.state.RobotState` + 客户端会话标志):
|
||
|
||
```
|
||
/enter accepted /exit accepted
|
||
[未进入] ───────────────▶ [已进入] ───────────────▶ [已退出]
|
||
│ accepted=false │ accepted=false │ 任何动作 accepted=false
|
||
└── 不变 └── 不变 └── 不变
|
||
```
|
||
|
||
[已进入] 状态内跟踪:position、测向机 channel、virtual_time_s。转移规则:
|
||
|
||
| 事件 | position | channel | virtual_time_s |
|
||
| --- | --- | --- | --- |
|
||
| /enter accepted | (0,0) | 1 | 0 |
|
||
| /measure accepted | ← 本次 position | ← 本次 channel | ← 响应值(=旧值+移动+切换+5) |
|
||
| /clear accepted | ← 本次 position | **不变** | ← 响应值(=旧值+移动+3或5) |
|
||
| /exit accepted | 不变 | 不变 | ← 响应值 |
|
||
| accepted=false / 网络 / 400 | 不变 | 不变 | 不变 |
|
||
|
||
虚拟时刻以最近一次 accepted=true 的响应为准;本地预测(timing 模块)只用于
|
||
规划与一致性核对,偏差记入 `RobotState.mismatches` 并写日志(strict 模式抛错)。
|
||
|
||
## 4. 计时示例逐步推演(附件1表2 = 附件2第10节)
|
||
|
||
| 步 | 指令 | 移动 | 换频道 | 动作 | 本步 | 累计 |
|
||
| --- | --- | --- | --- | --- | --- | --- |
|
||
| 1 | /enter | 0 | 0 | 0 | 0 | **0** |
|
||
| 2 | /measure (300,400) ch1 | 500m→100s | 0(初始即1) | 5 | 105 | **105** |
|
||
| 3 | /measure (300,400) ch2 | 0 | 1 | 5 | 6 | **111** |
|
||
| 4 | /clear (300,0) 目标ch3 | 400m→80s | 0(clear不切频道) | 3(未发现) | 83 | **194** |
|
||
| 5 | /measure (300,0) ch2 | 0 | 0(第3步后已是2,clear未改) | 5 | 5 | **199** |
|
||
| 6 | /exit | 0 | 0 | 0 | 0 | **199** |
|
||
|
||
验收:`uv run mcm26b sim-check` 通过完整客户端→本地模拟器链路复现
|
||
`[105, 111, 194, 199]`;`tests/test_protocol_timing.py::TimingModelTests`
|
||
逐步断言每个分项。
|
||
|
||
## 5. 客户端设计要点(P1 交付)
|
||
|
||
- **串行**:完全同步,一个动作未决不发下一个;
|
||
- **幂等重试**:请求体一次性序列化为 bytes,网络失败/429/500 时原样重发
|
||
(同 request_id 同内容),最多 `max_attempts` 次,指数退避;400/404/405/409/413/415 立即失败;
|
||
- **accepted=false**:抛 `ActionRejected`,状态不更新;virtual_time_s=0 不入时钟;
|
||
- **现实预算**:/enter 成功后 `deadline = monotonic() + remaining_real_duration_s`;
|
||
新动作前检查 `剩余 > reserve_exit_s`(默认 60s [工程]),/exit 豁免;
|
||
- **日志**:`SessionLog` 逐事件追加 JSONL(attempt/transport_error/accepted),
|
||
进程崩溃不丢已发记录,回放测试与 evaluation 复算共用同一格式;
|
||
- **本地预校验**:channel 范围、坐标有限性与 2e6 上限、robot_id/request_id 格式
|
||
在发送前拒绝(与服务器 400 语义一致,省一次往返)。
|
||
|
||
## 6. 策略层如何使用本协议(P3/P4 交付)
|
||
|
||
第 5 节的客户端是策略层唯一的事实来源;`belief`(候选集)、`planner`(选点与闭环)
|
||
不绕开它直接发请求。衔接约定:
|
||
|
||
- **串行不变**:闭环每个决策步只发一个动作,`measure`/`clear` 的返回值
|
||
(`MeasureOutcome`/`ClearOutcome`)是 belief 更新的唯一输入;
|
||
- **状态同步**:机器人位置/测向频道/虚拟时刻一律读 `client.state`,
|
||
频道规则(§3 状态机表)由状态机自动维护,策略层不自行推算;
|
||
- **预算驱动退出**:`DeadlineExceeded` 只在 `/exit` 之外的动作前抛出;
|
||
闭环捕获后立即 `/exit`(豁免检查),并把该局记为"未证明完成",
|
||
不与全清成功混记;
|
||
- **选点的接收保证**:选点器只在保证接收盘 B(mec, 1000-ρ) 内取点,
|
||
盘内任一点到候选集每点距离 < 1000 ≤ 最小接收半径,因此定位阶段
|
||
不会浪费 `no_signal`(ρ>1000 的一般候选集退化为探索 MEC 圆心,允许
|
||
no_signal,理由字段中如实标注);
|
||
- **清除证书**:候选集外包多边形半径(含近似误差与工程余量)
|
||
≤ 20m 才发起 `/clear`;证书有效但清除失败视为异常,先补测再重试,
|
||
不在同一证书上重复消耗 3 秒/次的失败清除;
|
||
- **停止条件与完成判定**(2026-09-11 用户决策后):找全证明达成
|
||
(每频道均"已清除或覆盖证明不存在",或清除数达 16 数量上界)后
|
||
**不提前退出**:按 `search.persistent_search`(默认开)只复扫未清除
|
||
频道,直到清除数达 16、实机预算耗尽或虚拟安全余量
|
||
(`virtual_reserve_s`,防触及附件2 §4.5 的 100 小时上限;
|
||
本地模拟器对达限后的动作以 accepted=false 兜底)不足——"扫到限时为止"。
|
||
summary 中 `completed` 表示任务证明完成(无中断且结束时每频道
|
||
CLEARED/ABSENT,或 16 清除);`accounted_virtual_time_s` 记录首次
|
||
证明找全的虚拟时刻(计算"平均定位清除时间"建议用它,而非复扫
|
||
耗尽后的最终时刻);`stop_reason` 记录循环结束原因。发现若干源、
|
||
连续 no_signal、候选采样为空都不是停止条件;
|
||
- **每步计时留痕**(同上决策):客户端每个被接受动作写一条 `step`
|
||
事件(实机墙钟耗时 `real_elapsed_s`/`real_since_enter_s` + 比赛虚拟
|
||
耗时分项 `virtual_cost`:移动=直线距离/5、切频道 1s、检测 5s、
|
||
清除成功 5s/未发现 3s),summary 以 `steps` 数组与 `step_totals`
|
||
汇总,并给出题面口径的 `avg_locate_clear_time_s`;
|
||
- **验收**:`tests/test_loop_q3.py` 在自建模拟器上覆盖 10/16/0 源、
|
||
近边界源、预算耗尽与故障注入;`uv run mcm26b run --robot-id <队号>
|
||
--self-test` 可离线复跑随机场景,正式模式去掉 `--self-test` 即对接
|
||
模拟器(日志用 `--log` 落盘,格式同 §5 JSONL)。
|
||
|
||
## 7. 定向扩展如何使用本协议(P5 交付)
|
||
|
||
问题4的定向源在**协议字段上与全向源完全不可区分**:`/measure` 仍只返回
|
||
no_signal/near/direction(direction 附 svd_deg),`/clear` 仍只看 20m 半径。
|
||
"该源是定向的"无法从报文读出,只能由观测推理。因此 §5/§6 的协议用法全部
|
||
照用,定向性只改变策略层的解释与决策:
|
||
|
||
- **发现阶段零改动**:49 点覆盖保证对定向源同样成立——源位于某网格单元
|
||
凸包内时,任意发射半平面(朝向 ±90°,含边界)必含至少一个顶点,且该
|
||
顶点距离 ≤ 700√2 ≈ 989.95 < 1000 ≤ 最小接收半径,覆盖阶段无需知道
|
||
源类型;
|
||
- **no_signal 联合约束基线**:定向源的 no_signal 约束的是"位置×半径×朝向"
|
||
联合假设,单独维护空间候选会被误删,基线**不据此删任何候选**
|
||
(`belief.update_measure` 对 no_signal 无操作);缺席证明仍只由完整覆盖
|
||
给出。唯一启用的推理:若 no_signal 发生在保证接收盘 B(mec, 1000-ρ) 内
|
||
(该盘内任一全向源必然可收),则只能由朝向遮挡解释 [观测推理]——该频道
|
||
记为"观测到定向",进入探点升级梯子:对 MEC 圆心取对跖方向复核(偏移
|
||
裁进保证接收盘),再绕 MEC、半径 = ρ 的圆环按 45° 步进探点。**几何边界
|
||
必须如实陈述**:环上可见弧没有下界——源可位于环上(|g−c| = ρ)且朝向
|
||
沿外切线,此时可见集退化为单个切点,固定步进环探可以全部漏过。梯子
|
||
只提高提前命中概率,不构成接收或命中保证;完备性完全由后备清除兜底。
|
||
被遮挡探点不消耗定位步数,单轮被遮挡探点(首次选点、对跖复核、环探
|
||
合计)达 `max_blocked_probes`(4 [工程])即转后备清除;
|
||
- **后备清除的协议用法**:定位停滞(候选集有界但未达 §6 清除证书)时,
|
||
对候选外包多边形构造 20m 有限圆盘覆盖(`fallback_clear_cells`:全局
|
||
半开网格索引、相交判定带正容差,格内任一点距某格心 ≤ 10√2 < 20m),
|
||
按"距机器人最近优先"逐格发 `/clear`(同频道、不同位置)——清除与朝向
|
||
无关 [题面],是定向源不依赖可见性的完备后备。两条预算硬门槛(任一
|
||
不满足即跳过并留痕,绝不盲扫):
|
||
- 格数上界:N ≤ `fallback_max_cells`(400 [工程]);外包框枚举先按
|
||
(⌈w/20⌉+2)(⌈h/20⌉+2) 做规模检查,宽候选集(如仅一次读数的 750m
|
||
截断角域,数千格)必须继续定位而非硬扫;
|
||
- 现实预算:N × `real_rtt_p95_s` + `reserve_exit_s` ≤ 剩余现实时间。
|
||
虚拟耗时上界 ≈ 移动 L/5 + 3N(全失败)或 3(N-1)+5(末次成功)。
|
||
后备 /clear 请求**计入总请求口径**:summary 的 `total_requests`
|
||
(measures+clears)与 `fallback_clear_attempts`(后备逐格尝试数)
|
||
如实上报——仅统计检测次数会明显低估现实耗时;
|
||
- **真源不误删**:no_signal、失败的 `/clear`(含后备逐格全失败)均不删
|
||
空间候选;后备全失败按守恒性破坏记异常(格心距格内任一点 < 20m,
|
||
理论上必命中);
|
||
- **停止条件与预算语义不变**:仍是清除数达 16、每频道均"已清除或覆盖
|
||
证明不存在"、现实预算耗尽(记未证明完成)三种;断连/协议错误/
|
||
守恒性破坏中断时同样尽力 /exit 并产出未完成 summary(`stop_reason`
|
||
记 `transport_failure`/`protocol_error`/`inconsistent_belief` 等,
|
||
附逐频道状态快照),不静默丢失会话;
|
||
- **验收**:`tests/test_loop_q4.py`(15 项:覆盖格无空洞与格线两侧保留、
|
||
退化多边形、格数上界公式;混合 10/16 源全清、朝向背离圆心的近边界源
|
||
不漏、批量种子回归;no_signal 不删候选;格数硬顶与预算跳过;后备
|
||
正路径命中与全失败异常留痕);`uv run mcm26b run --robot-id <队号>
|
||
--problem 4 --self-test` 可离线复跑混合场景(`--directional` 指定
|
||
定向源数),正式模式同 §6。
|