# mcm26b-referee — CUMCM 2026 B 题独立评测机 自建的官方模拟器复现:**模拟器引擎 + 机器狗 HTTP 接口 + 官方流程 WebUI**, 作为一个**独立包**发布(本目录),零第三方依赖,且**不导入任何参赛队代码**。 与参赛队实现(仓库根的 `mcm26b` 包)完全解耦:双方只通过附件2 定义的 HTTP 协议耦合——评测机换任何客户端都成立,参赛队代码对接官方模拟器也无需改动。 ```bash uv sync # 根工作区会同时挂载本包(dev 依赖) uv run python -m referee # WebUI http://127.0.0.1:8026,接口 :2026 uv run pytest referee/tests # 本包测试 ``` - 浏览器打开 `http://127.0.0.1:8026`:选择测试模块 → 确认开始 → 5 秒倒计时 → 接口开放,地图/状态/指令与反馈实时可见; - **回放器**("回放"Tab):按案例加载存档回放,也可**手动选定 JSONL 文件**(点击 选择或拖拽导入,纯前端解析;兼容评测机行为日志与参赛队客户端会话日志两种格式), 支持播放/暂停、逐步±、倍速快进、缩放/平移、进度条定位; - 机器狗程序照常对接 `http://127.0.0.1:2026`(如 `uv run mcm26b run --robot-id 2026001 --base-url http://127.0.0.1:2026`, 或一键 `uv run mcm26b run --robot-id 2026001 --local-judge`)。 ## 包结构 ``` referee/src/referee/ ├── spec.py # 题面常量:目标区域/频道/半径/限时/耗时模型([题面] 逐条标注) ├── rules.py # 服务端字段表、结果码与 HTTP 语义(附件2 §5-9 的可执行表格) ├── timing.py # 纯计时模型:移动+换频道+动作,整数微秒累计(105/111/194/199 锚点) ├── simulator.py # 评测引擎:观测判定、状态机、幂等、并发 409、截止与断连 ├── jsonio.py # 请求严格解析(BOM/重复键/NaN/深度)+ 响应规范化序列化 ├── interface.py # 机器狗接口完整边界(与传输无关;HTTP 服务器与内存直连共用) ├── server.py # 接口 HTTP 承载:仅回环、仅窗口期监听、关闭即断连 ├── session.py # 官方流程状态机:四模块/次数/倒计时/窗口/中止/结束原因/日志 ├── scenes.py # 一局真值的数据准备(q3 全向 / q4 混合,可固定种子) ├── webui.py # 界面服务器与 /ui/api/* └── static/ # 单页前端(原生 HTML/CSS/JS,离线可用) ``` ## 题意符合性对照(附件1《模拟器使用说明》/附件2《通信接口说明》) | 题意条款 | 实现 | 固化测试 | | --- | --- | --- | | 表1 动作与耗时(移动=距离/5、换频道 1s、检测 5s、清除 5s/3s,逐段整数微秒累计) | `timing.py` + `simulator.py` | `test_timing_examples.py`(105/111/194/199) | | 观测判定:no_signal/near(≤5m)/direction(±1° 误差、两位小数、[0,360) 归一化);定向 ±90° 含边界;清除 ≤20m 与朝向无关、仅一次 | `simulator.py` | `test_simulator_conformance.py` | | 协议边界:路径精确/仅 POST/415/413/严格 JSON/未知字段 accepted=false/幂等回放/409/并发 409 | `interface.py` + `simulator.py` | `test_interface_boundary.py`、`test_http_server.py`(真实 HTTP) | | 时限:5s 倒计时、25min 窗口、/enter 后 20min(取早)、虚拟 100h;截止后接口关闭、已登记动作允许完成 | `session.py` 看门狗 + `simulator.py` | `test_session_flow.py` | | 四个测试模块;演练不限次;正式各 3 次、启动成功即占用、中止仍占用;正式开始前再次确认 | `session.py` + WebUI | `test_session_flow.py` | | 指令与反馈:默认最新 1000 条、可调 100..5000、不限请求总数、完整行为入日志 | `session.py` + WebUI 设置 | `test_session_flow.py` | | 中止测试需两次确认;结束后不能复位或继续,只能开始新一局;结束原因显示 | WebUI + `session.py` | `test_session_flow.py` | | 测试案例编码;演练结束显示 总数/全向/定向;正式任何界面不显示真值 | `session.py` 真值策略 + WebUI | `test_session_flow.py` | | 接口地址默认 127.0.0.1:2026、只监听回环、空闲可改端口;非开放期连接失败属正常 | `server.py` + WebUI 设置 | `test_http_server.py` | | robot_id/request_id 格式(1..64/128 字节、无控制/格式字符);arena_id="default" | `simulator.py` + `session.py` | `test_simulator_conformance.py` | | 响应 virtual_time_s 微秒累计、最多 6 位小数去尾零;accepted=false 时恒为 0 | `jsonio.py` + `simulator.py` | `test_jsonio.py` | | 行为日志可导出;加密上传上限 2MB 提示 | WebUI 日志导出 + 大小告警 | `test_session_flow.py` | ### 自建差异声明(题面未规定,均为 [工程] 假设,不代表官方实现) - **联网登录/官方校时/17:30 启动截止/加密上传队列**:属官方服务器侧策略,本地评测机 不实现;robot_id 由界面填写并照常参与逐字节校验。 - **示向度误差场**:同一(频道,位置)误差由种子确定性决定(重复测量不变, 题面仅要求"同一地点误差不变"),不同位置独立白噪声;分布不代表官方。 - **场景分布**:源位置在目标圆域内均匀、r_eff 在 [1000,1500] 均匀、频道无重复抽样; q4 定向源数量默认 3;均可用种子复现。 - **429 流量保护/幂等记录上限**:正常串行合法请求无速率上限 [题面],本地不设限。 - **案例编码格式**:4×4 大写字母数字(去易混字符),格式自拟。