refactor(arch): redesign the code structure

This commit is contained in:
Horldsence
2026-09-10 22:38:27 +08:00
parent 21e8a8bf52
commit 83d2e24448
42 changed files with 2514 additions and 251 deletions
+123
View File
@@ -0,0 +1,123 @@
# 协议规则表、字段表与状态机(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 语义一致,省一次往返)。
+12 -12
View File
@@ -182,15 +182,15 @@ $$
## 八、运行方式和代码入口
核心算法仅用Python标准库,支持Python3.10及以上;本次在Python3.12.14中验证。在项目根目录运行:
核心算法仅用Python标准库,支持Python3.10及以上;工程依赖仅 `requests`(测试另需 `pytest`)。在项目根目录运行:
```powershell
python -m geometry examples/q1/triangle.json -o output/q1/triangle_recomputed.json
python main.py examples/q1/normal.json
python -m geometry examples/q1/normal.json --rounding-margin-deg 0.005
python -m unittest discover -s tests -v
python scripts/build_q1_demo.py
python scripts/benchmark_q1.py
uv run python -m mcm26b.geometry examples/q1/triangle.json -o output/q1/triangle_recomputed.json
uv run mcm26b q1 examples/q1/normal.json
uv run python -m mcm26b.geometry examples/q1/normal.json --rounding-margin-deg 0.005
uv run pytest
uv run python scripts/build_q1_demo.py
uv run python scripts/benchmark_q1.py
```
输入JSON的最小格式:
@@ -209,22 +209,22 @@ python scripts/benchmark_q1.py
| 文件 | 职责 |
| --- | --- |
| `geometry/localization.py` | 测向角域、半平面、区域分类、直径和包围圆 |
| `geometry/planning.py` | 问题二首次测向的距离保证候选圆盘;独立于问题一模型 |
| `geometry/__main__.py`、`main.py` | JSON命令行输入输出 |
| `src/mcm26b/geometry/localization.py` | 测向角域、半平面、区域分类、直径和包围圆 |
| `src/mcm26b/geometry/planning.py` | 问题二首次测向的距离保证候选圆盘;独立于问题一模型 |
| `src/mcm26b/geometry/__main__.py`、`src/mcm26b/cli.py` | JSON命令行输入输出 |
| `tests/test_geometry.py` | 解析反例、随机几何、独立裁剪对照与退化测试 |
| `tests/test_practice_replay.py` | 离线复算演练计时与观测结果,不连接模拟器 |
| `tests/test_geometry_certificates.py` | 增量圆与穷举法对照、覆盖证据、清除阈值、舍入选项及首次选点证书 |
| `scripts/benchmark_q1.py` | 局部性能对比与官方演练误差界敏感性复算 |
| `scripts/capture_practice.py` | 在已经启动且已核验的演练中采集少量测向 |
| `scripts/build_q1_demo.py` | 用Python算法生成案例、结果和交互图数据 |
| `visualization/q1.template.html` | 可视化源模板;不另写一套求解算法 |
| `scripts/assets/q1.template.html` | 可视化源模板;不另写一套求解算法 |
| `output/q1/question1.html` | 可直接打开的离线交互式模拟,所有数据已内嵌 |
如需再次采集,先在模拟器明确启动“问题3演练测试”,等待接口就绪,读取案例编码,然后运行:
```powershell
python scripts/capture_practice.py --robot-id 你的参赛队号 --practice-confirmed --case-code 界面中的演练案例编码
uv run python scripts/capture_practice.py --robot-id 你的参赛队号 --practice-confirmed --case-code 界面中的演练案例编码
```
接口协议没有返回演练/正式模式的字段,`--practice-confirmed` 是操作者确认已核验界面的显式参数,不能替代界面核查。采集脚本没有启动测试的功能,没有正式模式选项,只会调用 `/enter`、`/measure`、`/exit`。禁止在正式测试会话中运行。