mcm26b-referee — CUMCM 2026 B 题独立评测机
自建的官方模拟器复现:模拟器引擎 + 机器狗 HTTP 接口 + 官方流程 WebUI,
作为一个独立包发布(本目录),零第三方依赖,且不导入任何参赛队代码。
与参赛队实现(仓库根的 mcm26b 包)完全解耦:双方只通过附件2 定义的
HTTP 协议耦合——评测机换任何客户端都成立,参赛队代码对接官方模拟器也无需改动。
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 TEAM-0001 --base-url http://127.0.0.1:2026, 或一键uv run mcm26b run --robot-id TEAM-0001 --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 大写字母数字(去易混字符),格式自拟。