8.4 KiB
执行时间与执行步骤
配套插图:
figures/steps-state-machine.png(状态机与动作回合)、figures/timing-attachment-example-105-111-194-199.png(附件示例逐步推演)、figures/timing-measured-q3-10-sources.png(实测一局的时序分解)。 复现:uv run python scripts/build_figure_data.py --only steps-state-machine,timing-attachment-example-105-111-194-199,timing-measured-q3-10-sources
本文说明每一步做什么、每步花多少虚拟时间、这些数字如何验证。
规则原文见 issue/ 下附件1 表1、表2 与附件2 第 4、10 节;
可执行表格在 src/mcm26b/protocol/rules.py 与 src/mcm26b/config.py 的 CostConfig。
1. 计时模型
忽略微秒量化时,一次会话的总虚拟时间为
T=\underbrace{\frac{L}{5}}_{\text{移动}}+\underbrace{5N_m}_{\text{检测}}
+\underbrace{N_s}_{\text{换频}}+\underbrace{5N_c^{+}}_{\text{清除成功}}
+\underbrace{3N_c^{-}}_{\text{清除未发现}},
\qquad L=\text{路径长度(米)},\ 5\ \text{m/s}.
规则:
- 移动按两点间直线距离计,不计加减速;
- 换频只在
/measure触发,任意两频道间固定 1 s,/clear不切换频道; - 检测固定 5 s,与结果无关,
direction与no_signal同价; - 清除成功 5 s(光学定位 3 s + 激光清除 2 s),未发现目标 3 s(仅光学定位);
/enter、/exit不产生虚拟时间;- 访问顺序是开放路径问题,起点固定、终点自由,不必回到起点。
2. 执行步骤
2.1 会话级状态机
| 状态 | 进入条件 | 允许的动作 | 退出条件 |
|---|---|---|---|
IDLE |
界面待机(演练不限次 / 正式各 3 次) | — | 点击"开始测试" |
COUNTDOWN |
数据准备完成 | —(接口未开放) | 5 s 倒计时结束 |
OPEN |
倒计时结束,接口在 :2026 开放 |
/enter → /measure /clear → /exit |
25 分钟窗口到期 / 源全清后主动 /exit / 中止 |
CLOSED |
窗口到期或进程退出 | —(连接关闭,已登记动作可完成) | — |
现实预算与现实窗口是两条独立时钟:/enter 成功后 max_real_duration_s = 1200 s
起算(程序侧),测试窗口 test_window_s = 1500 s(服务端侧)。
客户端维护本地截止时钟,剩余不足 60 s(reserve_exit_s,工程参数)时不再发起新动作,
只保证 /exit 能发出去。
2.2 单次动作回合(严格串行)
- 本地预校验:
channel ∈ [1,20]、坐标有限且 $\lvert x\rvert,\lvert y\rvert\le 2\times10^6$、request_id长度与字符集,与服务器 400 语义一致,省一次往返; - 串行发送:同一时刻只有一个未决请求,不并发(并发会得到 409);
- 等服务端计时:
accepted=true才更新状态,虚拟时刻取响应中的virtual_time_s, 不做本地累加;accepted=false抛ActionRejected,状态不更新,virtual_time_s=0不计入时钟; - 更新本地状态:位置、当前频道、剩余现实预算,追加 JSONL 会话日志;
- 异常处理:网络错误、429、500 时同
request_id同内容重发 (请求体一次性序列化,最多max_attempts次,指数退避); 400、404、405、409、413、415 立即失败。
重发不会重复计时,服务端按 request_id 幂等。
2.3 任务类型
| 任务 | 触发 | 动作序列 | 虚拟成本 |
|---|---|---|---|
| 覆盖扫描 | 某频道尚未被任何站点覆盖 | 移动到覆盖点 → /measure |
距离/5 + 5 s(+ 换频 1 s) |
| 定位交会 | 某频道收到 direction |
移动到第二检测点 → /measure |
距离/5 + 5 s(+ 1 s) |
| 清除 | 同频道候选区域 ρ ≤ 20 m | 移动到 MEC 圆心附近 → /clear |
距离/5 + 5 s 或 3 s |
| 后备盲清(问题四) | 联合信念坍缩到有限格 | 依次到可疑格 /clear |
距离/5 + 3 s 或 5 s |
| 机会测量 | 已停靠且该频道待测 | 原地 /measure |
5 s |
任务表排序是开放路径问题,默认用滚动 2-opt 与 Or-opt;
精确 Held-Karp 与路线感知探点在本模拟器上为负收益,默认关闭,见
第三四问图论与动态规划优化方案.md §8.6。
3. 附件计时示例逐步推演(105 / 111 / 194 / 199)
| 步 | 指令 | 移动 | 换频 | 动作 | 本步 | 累计 |
|---|---|---|---|---|---|---|
| 1 | /enter |
0 | 0 | 0 | 0 | 0 |
| 2 | /measure (300,400) ch1 |
500 m → 100 s | 0(初始即 1) | 5 | 105 | 105 |
| 3 | /measure (300,400) ch2 |
0 | 1 | 5 | 6 | 111 |
| 4 | /clear (300,0) ch3 |
400 m → 80 s | 0 | 3(未发现) | 83 | 194 |
| 5 | /measure (300,0) ch2 |
0 | 0(第 3 步后已是 2) | 5 | 5 | 199 |
| 6 | /exit |
0 | 0 | 0 | 0 | 199 |
验证方式:
uv run mcm26b sim-check # 客户端 → 本地模拟器链路,输出 [105, 111, 194, 199]
uv run pytest tests/test_protocol_timing.py -q # 逐步断言移动、换频、动作三个分项
4. 实测时序分解
对评测机留档的每一局,scripts/build_figure_data.py 按与 referee.simulator
同源的规则逐动作复算,并检查分项之和是否等于响应中的 virtual_time_s。
4.1 问题三演练局 BBM9-HZG2-77N5-NKKC(10 源全清)
| 项 | 数值 | 占比 |
|---|---|---|
| 总虚拟时间 | 3134.643 s | 100% |
| 移动(里程 11.9 km) | 2379.6 s | 75.9% |
| 检测与清除动作(119 + 10 次) | 645.0 s | 20.6% |
| 换频(110 次) | 110.0 s | 3.5% |
| 现实耗时 | 1.889 s | — |
4.2 问题四演练局 GM56-DFXZ-XKDM-XYV7(12 源,含 3 个定向源)
| 项 | 数值 | 占比 |
|---|---|---|
| 总虚拟时间 | 8703.766 s | 100% |
| 移动(里程 32.4 km) | 6489.8 s | 74.6% |
| 检测与清除动作(338 + 79 次) | 1951.0 s | 22.4% |
| 换频(263 次) | 263.0 s | 3.0% |
| 现实耗时 | 3.397 s | — |
4.3 结论
- 移动占约 75%,优化重点是路径长度,这解释了七点计划(7 点、6.96 km 巡回) 优于 49 点网格的原因;
- 动作占约 21%,检测 5 s 是下限,因此找全后立即退出比复扫更省时间;
- 现实耗时与成绩无关,本地回环为毫秒级,官方口径取虚拟时间, 程序预算(1200 s)只用于限制计算耗时。
4.4 复现命令
# 用评测机留档重画(含真值)
uv run python scripts/build_figure_data.py --only timing-measured-q3-10-sources,\
replay-webui-q3-omnidirectional,replay-webui-q4-directional
# 归档新日志,评测机数据被清理后仍可复现插图
uv run python scripts/archive_replay_logs.py --list
uv run python scripts/archive_replay_logs.py BBM9-HZG2-77N5-NKKC
# 按官方口径逐微秒复算(接受客户端 JSONL 或评测机 log.jsonl)
uv run python scripts/analyze_run_timing.py referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl
uv run python scripts/analyze_run_timing.py examples/replay/*.jsonl --json /tmp/timing.json
uv run python scripts/verify_q3_practice.py output/q3/practice/<本局目录>
5. 时间预算与安全阀
| 约束 | 值 | 来源 | 客户端行为 |
|---|---|---|---|
| 移动速度 | 5 m/s | 题面 | 路径长度决定虚拟时间 |
| 检测 / 换频 | 5 s / 1 s | 题面 | 避免高频扫频 |
| 清除成功 / 未发现 | 5 s / 3 s | 题面 | 盲清代价可控 |
| 测试窗口 | 1500 s(现实) | 题面 | 窗口到期接口关闭 |
| 程序预算 | 1200 s(现实) | 题面 | /enter 后本地时钟起算 |
| 退出预留 | 60 s | 工程 | 剩余不足时只允许 /exit |
| 虚拟世界限时 | 360000 s | 题面 | 实测最坏为 1e4 量级,未触及 |
| 行为日志上限 | 2 MB | 题面 | 控制请求数 |
安全阀见 algo-3.md §7.2 与 algo-4.md §5.2:
预算将尽、连续无新发现或联合信念长时间不收敛时,转入最低成本收尾,
先清除并登记已有发现,再 /exit,并写入 stop_reason。


