Files
mcm-26-b/docs/协议规则表与状态机.md
T

124 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 协议规则表、字段表与状态机(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 语义一致,省一次往返)。