Files
mcm-26-b/docs/协议规则表与状态机.md
T
2026-09-11 02:32:31 +08:00

13 KiB
Raw Blame History

协议规则表、字段表与状态机(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 秒/次的失败清除;
  • 停止条件(全部显式实现):清除数达 16(源数上界证明)、 每频道均"已清除或覆盖证明不存在"、现实预算耗尽。发现若干源、 连续 no_signal、候选采样为空都不是停止条件;
  • 验收: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。