179 lines
8.3 KiB
Markdown
179 lines
8.3 KiB
Markdown
# 执行时间与执行步骤
|
||
|
||
> 配套插图:[`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` 不产生虚拟时间;
|
||
- 访问顺序是开放路径问题,起点固定、终点自由,不必回到起点。
|
||
|
||

|
||
|
||
---
|
||
|
||
## 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 单次动作回合(严格串行)
|
||
|
||

|
||
|
||
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 |
|
||
|
||

|
||
|
||
验证方式:
|
||
|
||
```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 | — |
|
||
|
||

|
||
|
||
### 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`。
|