# 执行时间与执行步骤 > 配套插图:[`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/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` 不产生虚拟时间; - 访问顺序是开放路径问题,起点固定、终点自由,不必回到起点。 ![执行步骤:状态机与动作回合](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/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 | — | ![实测执行时序分解](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/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`](algo-3.md) §7.2 与 [`algo-4.md`](algo-4.md) §5.2: 预算将尽、连续无新发现或联合信念长时间不收敛时,转入最低成本收尾, 先清除并登记已有发现,再 `/exit`,并写入 `stop_reason`。