Files
mcm-26-b/docs/执行时间与执行步骤.md
T
2026-09-13 08:52:23 +08:00

179 lines
8.3 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.
# 执行时间与执行步骤
> 配套插图:[`figures/steps-state-machine.png`](figures/steps-state-machine.png)(状态机与动作回合)、
> [`figures/timing-attachment-example-105-111-194-199.png`](figures/timing-attachment-example-105-111-194-199.png)(附件示例逐步推演)、
> [`figures/timing-measured-q3-10-sources.png`](figures/timing-measured-q3-10-sources.png)(实测一局的时序分解)。
> 复现:`uv run python scripts/make_figures.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` 不产生虚拟时间;
- 访问顺序是开放路径问题,起点固定、终点自由,不必回到起点。
![执行步骤:状态机与动作回合](figures/steps-state-machine.png)
---
## 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 单次动作回合(严格串行)
![动作回合](figures/steps-state-machine.png)
1. 本地预校验:`channel ∈ [1,20]`、坐标有限且 $\lvert x\rvert,\lvert y\rvert\le 2\times10^6$、
`request_id` 长度与字符集,与服务器 400 语义一致,省一次往返;
2. 串行发送:同一时刻只有一个未决请求,不并发(并发会得到 409);
3. 等服务端计时:`accepted=true` 才更新状态,虚拟时刻取响应中的 `virtual_time_s`,
不做本地累加;`accepted=false` 抛 `ActionRejected`,状态不更新,
`virtual_time_s=0` 不计入时钟;
4. 更新本地状态:位置、当前频道、剩余现实预算,追加 JSONL 会话日志;
5. 异常处理:网络错误、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`](第三四问图论与动态规划优化方案.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 |
![附件计时示例逐步推演](figures/timing-attachment-example-105-111-194-199.png)
验证方式:
```bash
uv run mcm26b sim-check # 客户端 → 本地模拟器链路,输出 [105, 111, 194, 199]
uv run pytest tests/test_protocol_timing.py -q # 逐步断言移动、换频、动作三个分项
```
---
## 4. 实测时序分解
对评测机留档的每一局,`scripts/make_figures.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 | — |
![实测执行时序分解](figures/timing-measured-q3-10-sources.png)
### 4.3 结论
1. 移动占约 75%,优化重点是路径长度,这解释了七点计划(7 点、6.96 km 巡回)
优于 49 点网格的原因;
2. 动作占约 21%,检测 5 s 是下限,因此找全后立即退出比复扫更省时间;
3. 现实耗时与成绩无关,本地回环为毫秒级,官方口径取虚拟时间,
程序预算(1200 s)只用于限制计算耗时。
### 4.4 复现命令
```bash
# 用评测机留档重画(含真值)
uv run python scripts/make_figures.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`](algo-3.md) §7.2 与 [`algo-4.md`](algo-4.md) §5.2:
预算将尽、连续无新发现或联合信念长时间不收敛时,转入最低成本收尾,
先清除并登记已有发现,再 `/exit`,并写入 `stop_reason`。