Skip to content

dev: Robots adaptation after rmuc 2026 - #101

Closed
creeper5820 wants to merge 53 commits into
mainfrom
dev/robots
Closed

dev: Robots adaptation after rmuc 2026#101
creeper5820 wants to merge 53 commits into
mainfrom
dev/robots

Conversation

@creeper5820

@creeper5820creeper5820 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

变更摘要

  • 适配 RMUC 2026 机器人架构,新增 DeformableInfantryOmniBSentrySteeringHeroLittle 等硬件组件。
  • 重构底盘、悬挂、云台、射击和遥控器控制逻辑,新增爬升、主动悬挂、导航、自动瞄准和功率管理支持。
  • 新增 PX4 视觉桥接、哨兵决策、数据采集、扫频测试和静态扭矩测试组件。
  • 新增摩擦轮速度识别、重力扭矩拟合和灰盒模型拟合工具。
  • 扩展 ChassisModeSentryEvent、裁判系统状态字段和哨兵指令协议。
  • 更新多种机器人启动配置,新增自动瞄准测试、导航测试和六轮摩擦轮舵轮配置。
  • 升级 librmcs SDK,新增 mavlinknav_msgsament_index_cpp 依赖。
  • 新增 rmcs-cliautoaim-debug、上下文管理、远程扫描和容器 Agent 启动工具。
  • 更新 Docker 镜像工作流,使 .script/template/ 内容变更时触发构建。
  • 新增 CsvWriterNodeMixin、协程调度器和环形缓冲区查看接口等基础设施。
  • 删除旧版 Steering、DeformableInfantryV2 及相关底盘插件和配置。

qzhhhiand others added 30 commits July 14, 2026 09:13
2、interfere clean
3、six_friction: rename and reset parameter
4、putter return
…for temporary debugging by qzhhhi · Pull Request #55 · Allia)
- Sync both omni and omni-b to BottomBoard/TopBoard/ImuBoard architecture
- Update board serial filters and DeformableSuspension config in YAMLs
- Remove Vt13/RemoteControl; use Dr16 direct /remote/* outputs
- Clean up unrelated changes from feat/deformable-infantry
floatpigeonand others added 23 commits July 19, 2026 20:11
* feat: Add spin stuck detection for chassis and develop nav gimbal control
* feat: Getter for node mixin
* wip: Chassis stuck detection
* feat: Support nav fusion control with joystick
* feat: Update remote control timeout logic
* chore: Update sentry config
* build: Add detect path to update-image workflow
* chore: Update auto aim v2
* wip: Fill basic context and utils for climber
* refactor: Climber for sentry
* feat(climber): add kRetracted state for stick auto-retract with stall detection
- StickGroup: add kRetracted state with speed_rise + rise_torque_limit + hold_torque
- kRetracted performs PID ascent, switches to constant hold_torque (0.25Nm) on stall
- release_chassis() now sets track→kFree + stick→kRetracted for safe idle state
- Add hold_torque param (default 0.25) to YAML config
* wip: Cleanup code
* feat: Adapt climb request from navigation
* chore: Update auto aim v2
* wip: Improve climber
---------
Co-authored-by: zlq040222 <1542498005@qq.com>
* chore: Clean up code
* feat: Add mavlink to dockerfile
* feat: Adapt autoaim on flight
* feat: Add flight odin support by mavlink component
* fix: Add px4_serial output (forgot in flight.cpp)
* chore: Update autoaim offset
* chore: Add submodule odin and hikcamera
* fix: Add autoaim param degraded_angle_speed,refactor:odin management in px4_vision_bridge
* chore: Remove hikcamera and odin_ros_driver submodules
* chore: Apply format
* chore: Remove unused cmake config
---------
Co-authored-by: creeper5820 <cqq1960180796@163.com>
* wip: Develop sentry climb and decision
* feat: Enable navigation and add referee enemy status outputs
- Enable SentryDecision and Navigation plugins in sentry config
- Parameterize steering wheel controller PID gains
- Add enemy outpost/base hp and damage difference outputs
* wip: Clean up pr impl and update auto aim v2
---------
Co-authored-by: creeper5820 <cqq1960180796@163.com>
* wip(robots): Enable sentry navigation, add navigation supercap boost and refine climber status
* wip(robots): Add leave phase after sentry climber landing
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

本次变更扩展 RMCS 的开发工具、硬件接入、底盘与云台控制、射击控制、裁判交互、系统辨识和运行配置,并更新消息接口、插件注册、SDK 依赖及公共工具。

Changes

RMCS 工具与运行环境

Layer / File(s)Summary
开发脚本与容器命令
.script/*, .github/workflows/*, Dockerfile, .gitmodules
新增自动瞄准调试、上下文管理、tmux 控制和系统辨识脚本。扩展容器启动、重建和 Agent 命令。新增 MAVLink 依赖、子模块配置和镜像触发路径。
远程扫描与终端交互
.script/scan-remote, .script/remote-status
更新私有网络扫描范围、退出码、线程池清理和非 TTY 渲染逻辑。调整状态标题。

控制与硬件集成

Layer / File(s)Summary
底盘模式与悬挂控制
rmcs_ws/src/rmcs_core/src/controller/chassis/*
新增可变形底盘模式管理、主动悬挂、履带组、撑杆组和协程调度器。更新导航、爬升、旋转卡死检测和功率控制。
云台与射击控制
rmcs_ws/src/rmcs_core/src/controller/gimbal/*, rmcs_ws/src/rmcs_core/src/controller/shooting/*
新增偏心双云台求解器、编码器刹车状态机和姿态保持逻辑。更新单发控制、推弹杆状态、摩擦轮工作速度、热量边沿检测和射击记录。
板卡与设备接入
rmcs_ws/src/rmcs_core/src/hardware/*
迁移多个硬件组件到 RmcsBoardLite 回调接口。新增 VT13、RemoteControl、状态监控、DJI 电机 CAN ID 管理及 PX4 视觉桥接。
插件与构建依赖
rmcs_ws/src/rmcs_core/plugins.xml, rmcs_ws/src/rmcs_core/CMakeLists.txt, rmcs_ws/src/rmcs_core/package.xml
更新 SDK 版本和运行时依赖。移除旧插件注册,并注册新硬件、控制器、采集器和测试组件。

配置、消息与裁判界面

Layer / File(s)Summary
机器人启动配置
rmcs_ws/src/rmcs_bringup/config/*
新增自动瞄准、导航、PX4、Sentry 爬升、数据采集和系统辨识配置。更新变形底盘、云台、摩擦轮、热量及关节控制参数。删除旧 steering 配置。
裁判协议与交互决策
rmcs_ws/src/rmcs_core/src/referee/*, rmcs_ws/src/rmcs_msgs/include/rmcs_msgs/*
新增 SentryEventSentryCommandSentryInfo。解析哨兵状态及生命值数据。新增 SentryDecision。扩展底盘模式。
裁判 UI 与公共工具
rmcs_ws/src/rmcs_core/src/referee/app/*, rmcs_ws/src/rmcs_utility/include/*
新增动画开关、CSV 写入、节点参数混入和环形缓冲区查看接口。更新自动瞄准、底盘悬挂、超级电容及机器人状态显示。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers:qzhhhi

Poem

我是兔子,蹦进新代码,
控制器排队把信号接好。
云台旋转,履带向前,
CSV 记录每次心跳。
小胡萝卜献给新插件,
RMCS 今夜运行更早。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 1.39% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passed标题概括了本次面向 RMUC 2026 的机器人适配改动,与新增硬件、控制器、配置和工具的主要内容相关。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/robots

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp (1)

285-297: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

下降沿相机同步的 GPIO 回调缺少电平确认。 三个硬件文件都用 falling_edge = true, rising_edge = false 的 GPIO 配置来捕获相机曝光时间戳,但只有 sentry.cppGimbalBoard::gpio_digital_read_result_callback(第 230-231 行)在使用时间戳之前检查了 if (data.high) return;。另外两处缺少该检查。如果板卡在配置生效前或因其他原因上报一次高电平采样,这两处会发出错误的曝光时间戳,导致自瞄的时间对齐出错。

  • rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L285-L297:在第 287 行的 GPIO 描述符判断之后、时间戳判空之前,增加 if (data.high) return;
  • rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp#L501-L515:在第 506 行 kUart1Tx 分支内,于第 507 行的时间戳判空之前增加 if (data.high) return;。注意不要影响第 504-505 行 kUart1Rx 分支,那里 data.high 是有效的传感器电平。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp` around lines 285 - 297,
在 omni_infantry.cpp 的 gpio_digital_read_result_callback(285-297)中,于 kUart0Tx
判断后、时间戳判空前增加 data.high 的提前返回;在 steering-hero-little-six-friction.cpp 的
gpio_digital_read_result_callback(501-515)中,仅对 kUart1Tx 分支执行同样检查,勿修改 kUart1Rx
分支。
rmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpp (1)

181-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

AUTO 分支覆盖计算结果,导致共享 PID 积分饱和。

第 182 行至第 190 行计算 angular_velocity,第 192 行把它无条件置为 0。计算结果被丢弃,但 update_following_angular_velocity 有副作用:它调用 following_velocity_controller_.update(err)。该 PID 对象在 STEP_DOWNALIGNMENTALIGNMENT_POWEREDCLIMBLAUNCH_RAMP 分支以及攀爬分支(第 173 行)中共用。

因此在 AUTO 模式下 PID 会持续积分而输出被丢弃。切换到上述任一模式时,第一帧输出会包含累积的积分项,产生突发角速度。

请在 AUTO 分支重置 PID,并移除已失效的计算。

🐛 建议的修复
 switch (*mode_) {
case rmcs_msgs::ChassisMode::AUTO: {
- angular_velocity =- update_following_angular_velocity(StepDownFacing::BACK, chassis_control_angle);-- // Keep AUTO rear-following gentle at low translation speed and fully enabled at max.- const double measured_translational_speed =- chassis_velocity_feedback_ready_ ? chassis_velocity_->vector.head<2>().norm()- : translational_velocity.norm();- angular_velocity *=- std::clamp(measured_translational_speed / translational_velocity_max, 0.0, 0.3);-- angular_velocity = 0.0;-+ // AUTO 模式不做随动,重置 PID 以避免切换模式时积分突变。+ following_velocity_controller_.reset();+ angular_velocity = 0.0;
} break;

如果保留随动是后续计划,请改用条件开关而不是直接覆盖。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpp`
around lines 181 - 194, 在英雄底盘控制器的 AUTO 分支中移除无效的跟随角速度计算及其速度缩放逻辑,并在该分支通过
following_velocity_controller_ 重置共享 PID,确保切换到其他模式时不会携带 AUTO 期间累积的积分项;保留
angular_velocity 为零的 AUTO 行为。
rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp (1)

61-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

E 键与 C 键从 ENCODER 切回 IMU 的行为不一致。

第 72-75 行的 C 键切换会设置 switch_encoder_to_imu_by_c,第 81 行随后调用 enter_imu_hold_current_pose() 保持当前姿态。

第 61-68 行的 E 键在 gimbal_mode_keyboard_ == GimbalMode::IMU 为假时也会切回 IMU,但不设置该标志。此时 imu_gimbal_solver_ 已在 ENCODER 期间被 SetDisabled(第 91 行),因此 update_imu_control() 第 130-131 行会执行 SetToLevel{},云台立刻跳到水平位置。

请确认该差异是有意设计。如果两个按键都表示"退出编码器模式",建议让 E 键也走 enter_imu_hold_current_pose()

🐛 建议修复
+ bool switch_encoder_to_imu_by_c = false;+
if (!last_keyboard_.e && keyboard_->e) {
if (gimbal_mode_keyboard_ == GimbalMode::IMU) {
encoder_init_pitch_ = keyboard_->ctrl ? kCtrlEInitPitch : kEInitPitch;
gimbal_mode_keyboard_ = GimbalMode::ENCODER;
} else {
gimbal_mode_keyboard_ = GimbalMode::IMU;
+ switch_encoder_to_imu_by_c = true;
}
}
-- bool switch_encoder_to_imu_by_c = false;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp`
around lines 61 - 82, 使 E 键从 ENCODER 切换回 IMU 时与 C 键保持一致:在 gimbal_mode_keyboard_
的 E 键切换逻辑中设置 switch_encoder_to_imu_by_c 所对应的切换标志,确保后续 IMU 控制调用
enter_imu_hold_current_pose() 而不是 update_imu_control();保留 E 键从 IMU 切换到 ENCODER
的现有行为。
rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp (1)

70-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

friction_wheel_count_ != 6 时会写入空行。

log_text 初始化为空字符串。如果 friction_wheel_count_ 参数不是 6,条件不成立,第 78 行仍然写入日志文件并打印空行。日志文件会被无内容的行填充。

请让日志格式与轮数解耦,或者在计数不受支持时提前返回并打印一次告警。

🐛 建议修改
- if (friction_wheel_count_ == 6) {- log_text = fmt::format(- "{},{},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f}", *initial_speed_,- (int)velocities.size(), //- velocity_, excellence_rate_, pass_rate_, range_, range2_, velocity_max,- velocity_min);- }+ log_text = fmt::format(+ "{},{},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f}", *initial_speed_,+ (int)velocities.size(), //+ velocity_, excellence_rate_, pass_rate_, range_, range2_, velocity_max,+ velocity_min);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp` around
lines 70 - 78, Update the logging flow around the friction_wheel_count_ check so
unsupported counts do not write an empty line. Either generate a valid log
format independent of friction_wheel_count_ or return before log_stream_ writes
and emit a warning once for unsupported values; preserve normal logging for
count 6.
rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp (1)

195-207: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

弹丸击发检测中的轮索引不一致,并且 target_friction_velocity(2) 可能越界。

第 196 行和第 207 行改用 friction_velocities_[0],但第 201 行仍然用 target_friction_velocity(2) 作为阈值基准。若两轮的目标速度不同,判据会失真。

另外 target_friction_velocity(i) 直接对 std::vector 使用 operator[]。当 friction_wheels 配置为 1 或 2 个轮时,索引 2 越界,属于未定义行为。请把阈值基准改为索引 0。

🐛 建议修改
 if (primary_friction_velocity_decrease_integral_ < -14.0
- && last_primary_friction_velocity_ < target_friction_velocity(2) - 25.0)+ && last_primary_friction_velocity_ < target_friction_velocity(0) - 25.0)
fired = true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp`
around lines 195 - 207, Update the firing detection condition in the
friction-wheel controller to use target_friction_velocity(0) alongside
friction_velocities_[0]. Replace the current target_friction_velocity(2)
reference in the primary friction velocity decrease check to avoid an
out-of-bounds access and keep the threshold aligned with the monitored wheel.
🟠 Major comments (31)
.script/local-context-76-89 (1)

76-89: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

写入 FIFO 可能永久阻塞。

第 87 行向命名管道写入数据。如果此时没有进程打开该 FIFO 的读端,printf 会一直阻塞,脚本不会返回,也不会给出提示。第 77 行的 -p 检查只确认 FIFO 存在,不能确认存在读者。

建议加上超时保护,让用户在读端未就绪时得到明确的错误。

🔧 建议的修改
-printf '%s' "${value}" >"${fifo}"+if ! timeout 5 bash -c 'printf "%s" "$1" >"$2"' _ "${value}" "${fifo}"; then+ echo "Timed out writing to ${fifo}. Is rmcs-navigation reading the FIFO?" >&2+ exit 1+fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/local-context around lines 76 - 89, Add timeout protection around
the FIFO write in the local-context flow, using the existing fifo and value
variables, so printf cannot block indefinitely when no reader is connected. On
timeout, emit a clear error to stderr and exit nonzero; preserve the current
successful write and confirmation behavior.
.script/rmcs-cli-31-36 (1)

31-36: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

_list_pane_ids 未检查 tmux 是否成功,窗格不足时会抛出 IndexError

subprocess.run 失败时 result.stdout 为空字符串,"".strip().split("\n") 返回 [""]。第 76-79 行的 final[1]final[3] 会抛出 IndexError;即使不越界,pane ID 也会是空字符串,后续 send-keys -t "" 静默失败。

同样的假设出现在第 152-156 行的 run_control_panel

🛡️ 建议的校验
 def _list_pane_ids():
result = subprocess.run(
["tmux", "list-panes", "-t", SESSION, "-F", "#{pane_id}"],
capture_output=True, text=True
)
- return result.stdout.strip().split("\n")+ if result.returncode != 0:+ sys.exit(f"错误: 无法列出 tmux 窗格: {result.stderr.strip()}")+ ids = [line for line in result.stdout.strip().split("\n") if line]+ if not ids:+ sys.exit("错误: 未找到 tmux 窗格")+ return ids

并在第 75 行后校验数量:

 final = _list_pane_ids()
+ if len(final) < 4:+ sys.exit(f"错误: 期望 4 个窗格, 实际 {len(final)} 个")

Also applies to: 75-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/rmcs-cli around lines 31 - 36, Update _list_pane_ids to detect
unsuccessful tmux execution and return only non-empty pane IDs instead of
splitting an empty stdout into [""]. In the callers that index the result,
including the final pane setup around final[1] and final[3] and
run_control_panel, validate that the required pane count exists before indexing;
handle insufficient panes explicitly so no empty target reaches send-keys.
.script/scan-remote-595-604 (1)

595-604: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

行数减少时光标记账错误,下一帧渲染会错位。

分析当前帧的光标移动:

  1. 第 596 行上移 self._rendered_lines(记为 old)行,回到渲染区起点。
  2. 第 597-600 行输出 len(lines) 行,光标下移 len(lines) 行。
  3. 第 601-602 行为清理多余行再输出 old - len(lines) 个换行,光标继续下移。
  4. 光标最终位于起点下方 old 行,但第 604 行把 _rendered_lines 记为 len(lines)

下一帧只上移 len(lines) 行,比实际少 old - len(lines) 行。渲染区会向下漂移并留下残留文本。结果列表在选择过程中不会减少,但网络列表为空或 _promptclear_prompt 清除时会触发。

清理多余行后,请把光标移回内容末尾。

🐛 建议的修复
 for line in lines:
sys.stdout.write("\033[2K\r")
sys.stdout.write(line)
sys.stdout.write("\n")
- for _ in range(max(0, self._rendered_lines - len(lines))):+ extra = max(0, self._rendered_lines - len(lines))+ for _ in range(extra):
sys.stdout.write("\033[2K\r\n")
+ if extra:+ sys.stdout.write(f"\033[{extra}A")
sys.stdout.flush()
self._rendered_lines = len(lines)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/scan-remote around lines 595 - 604, Update the rendering logic
around the line-clearing loop in the scan UI so that, after clearing surplus
lines when the new result has fewer entries, the cursor is moved back to the end
of the current rendered content. Keep _rendered_lines synchronized with
len(lines), ensuring the next frame’s cursor-up operation starts from the
correct position.
.script/rmcs-cli-90-102 (1)

90-102: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

编译等待循环没有超时,并且状态判定不可靠。

两个风险:

  1. tmux capture-pane -p 只返回窗格当前可见区域。编译输出较多时,Summary: 行会滚出可见区域。循环随后永久执行,控制面板卡死。
  2. "failed" in result.stdout 对整屏文本做子串匹配。任意包含 failed 的编译日志行都会被判为失败,Summary: 0 packages failed 之类的正常输出同样命中。

建议改为轮询 .script/build-rmcs 的退出码(例如写入标记文件),并加入超时上限。

♻️ 建议的最小改动:增加超时
 log(" 等待编译完成...")
+ deadline = time.time() + 1800
while True:
result = subprocess.run(
["tmux", "capture-pane", "-t", _pane_build, "-p"],
capture_output=True, text=True
)
if "Summary:" in result.stdout or "failed" in result.stdout:
break
+ if time.time() > deadline:+ log("✗ 编译超时")+ return
time.sleep(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/rmcs-cli around lines 90 - 102, Update the build-wait loop in the
script around the tmux capture logic to use a bounded timeout and reliable build
completion status instead of scanning visible pane text for “Summary:” or
“failed”. Prefer polling the `.script/build-rmcs` process exit state via an
appropriate marker or status file, then preserve distinct success and failure
handling after completion.
rmcs_ws/src/rmcs_auto_aim_v2-1-1 (1)

1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

更新未检出子模块到目标提交。

rmcs_ws/src/rmcs_auto_aim_v2 当前检出的是 66863258...,与 .gitmodules 指向的远程 SHA 387b384e... 不一致,合并后会被当作脏工作树/未保存变更。清理或重置该子模块并重新提交到目标 gitlink。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_auto_aim_v2` at line 1, 将 rmcs_auto_aim_v2 子模块重置或更新到
.gitmodules 指向的目标提交 387b384e...,确保不再停留在 66863258...;清理子模块工作树后重新提交对应的
gitlink,避免合并后被识别为未保存变更。
rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp-170-182 (1)

170-182: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

限制格式化距离文本的写入长度。

std::format_to 会一直写入格式化结果,而 text 只分配 30 字节;robot_center_->norm() 只要值有限就会进入该路径,较大值会写出缓冲区。改用 std::format_to_n(..., kMaxTextLength, "{} | {:.1f}m", type, *distance),无距离时保留原 "{} | NONE""{} {}" 格式串即可;末尾嵌入的 \0 不是安全终止符。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp` around lines 170 -
182, 限制 set_distance_text 中的格式化写入长度:将有距离分支的 std::format_to 改为使用 kMaxTextLength
限制输出的 std::format_to_n,并保留 "{} | {:.1f}m" 格式;无距离分支继续使用现有格式但移除格式串末尾嵌入的
"\0",确保缓冲区不会越界且由字符串存储负责终止。
rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp-141-141 (1)

141-141: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

前爬升机构与底盘共用同一个功率上限值,会导致总功率超限。

第 140-141 行把同一个 power_limit 同时写入 /chassis/control_power_limit/chassis/climber/front/control_power_limit。这两个通道由两个独立的限功器消费:底盘功率控制器限制轮组,ChassisClimberFrontPowerLimiterchassis_climber_controller.cpp 第 170 行注册该输入)限制前爬升电机。

两者各自按满额上限工作,实际总功率可达该上限的两倍。爬坡时轮组和爬升机构同时出力,会超出裁判系统功率上限并造成扣血。

请在两者之间分配功率预算,例如按爬坡激活状态划分比例,或从底盘预算中扣除分给爬升机构的份额。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp` at
line 141, 调整 chassis power limit 分配逻辑,避免 chassis 与 front climber 各自获得完整的
power_limit;在相关控制更新函数中根据前爬升机构激活状态划分或扣除爬升功率份额,并分别写入 control_power_limit_
与前爬升限功器输入,确保两者合计不超过总功率上限。
rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp-188-193 (1)

188-193: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

ALIGNMENT 模式读取的是上一拍的底盘速度,且复位后为 NaN。

update_velocity_control() 先调用 update_translational_velocity_control(),再调用 update_angular_velocity_control(),最后才写入 chassis_control_velocity_->vector。因此第 263 行 chassis_control_velocity_->vector.head<2>() 读到的是上一拍的值。

reset_all_controls()*chassis_control_velocity_ 设为 {kNaN, kNaN, kNaN},并把模式设为 ALIGNMENT。当遥控器恢复后的第一拍进入 ALIGNMENT 分支时,speed 为 NaN,signed_angle 返回 NaN,following_velocity_controller_.update(-min) 得到 NaN。PID 的积分项会被 NaN 污染,后续输出会持续为 NaN,直到再次复位。

请把本拍计算出的平移速度传入角速度计算,并对非有限值做保护。

🐛 建议修复
 void update_velocity_control() {
auto translational_velocity = update_translational_velocity_control();
- auto angular_velocity = update_angular_velocity_control();+ auto angular_velocity = update_angular_velocity_control(translational_velocity);
chassis_control_velocity_->vector << translational_velocity, angular_velocity;
}
- double update_angular_velocity_control() {+ double update_angular_velocity_control(const Eigen::Vector2d& translational_velocity) {
double angular_velocity = 0.0;
@@
case ChassisMode::ALIGNMENT: {
- const auto speed = chassis_control_velocity_->vector.head<2>();+ if (!translational_velocity.array().isFinite().all())+ break;+ const auto speed = translational_velocity;
const auto line1 = Eigen::Vector2d{speed.x(), 0};

Also applies to: 262-275

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp` around
lines 188 - 193, 修复 update_velocity_control 与 update_angular_velocity_control
的数据流:将本拍计算出的 translational_velocity 传入角速度计算,避免 ALIGNMENT 读取
chassis_control_velocity_->vector 的上一拍或 NaN 值;同时在 ALIGNMENT 相关计算及传入
following_velocity_controller_ 前校验速度和角度等中间结果的有限性,遇到非有限值时使用安全的有限值并阻止 NaN 污染 PID
积分项。
rmcs_ws/src/rmcs_core/plugins.xml-6-9 (1)

6-9: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

补充 rmcs_core::test::output::AngleOutput 的插件导出注册。

plugins.xml 中该类型条目缺少对应的 PLUGINLIB_EXPORT_CLASS(...),后续插件加载会找不到该组件,需要补齐导出语句并确认对应源文件已编译。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/plugins.xml` around lines 6 - 9, 在插件注册配置中补充
rmcs_core::test::output::AngleOutput 的导出注册,并在其实现源文件中确保存在对应的
PLUGINLIB_EXPORT_CLASS 声明且该源文件已加入构建目标,使插件能够被正常加载。
rmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpp-181-220 (1)

181-220: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

协程内调用 stop_all() 会导致迭代器失效。

spin_once()std::erase_if(slots_, ...) 内部调用 slot->handle.resume()。如果被恢复的协程体内调用了 stop_all()slots_.clear() 会释放 vector 的存储,erase_if 随后继续使用已失效的迭代器,产生未定义行为。同时 slot 所指的 Slot 会被销毁,而 resume() 尚未返回,协程帧已被 Slot 析构函数 destroy()

急停清场是该调度器的既定用途,任务体内触发急停是自然用法。请增加重入保护,例如用标志记录“正在 spin”,在 spin 中调用 stop_all() 时只置位待清标志,在 spin_once() 结束后再执行清理。

🛡️ 建议方向
 auto spin_once() {
+ spinning_ = true;
slots_.insert(slots_.end(), pending_.begin(), pending_.end());
pending_.clear();
@@
+ spinning_ = false;+ if (stop_requested_) {+ stop_requested_ = false;+ slots_.clear();+ pending_.clear();+ return;+ }+
if (error)
std::rethrow_exception(error);
}
// 急停清场:销毁全部任务帧,协程局部变量正常析构
auto stop_all() {
+ if (spinning_) {+ stop_requested_ = true;+ return;+ }
slots_.clear();
pending_.clear();
}
private:
+ bool spinning_ = false;+ bool stop_requested_ = false;
std::vector<std::shared_ptr<Slot>> slots_{};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpp` around
lines 181 - 220, 为 spin_once() 增加“正在执行”状态保护,并让 stop_all() 在 spin
期间仅记录待清理请求,不直接清空 slots_ 或 pending_;在 spin_once()
完成遍历、异常处理前后选择安全时机执行延迟清理,并确保协程恢复期间 Slot 及其协程帧保持有效,非 spin 期间仍立即清理。
rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp-102-110 (1)

102-110: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

超时互锁在只注册一路遥控设备的机器人上会完全禁用失联保护。 根因在 RemoteControl::update_timeout_interlock():它只在对方设备当前 valid() 时才允许本设备启用超时。本 PR 中有三个机器人只注册了 DR16,从不注册 VT13。对这些机器人,vt13_ 恒为 nullptrvt13_ok 恒为 false,因此 DR16 的超时检测被永久关闭。DR16 链路断开后 valid() 继续返回 truebuild_snapshot() 持续输出断线前最后一帧的摇杆值与拨杆值,底盘和云台会按最后的指令持续运动。

  • rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp#L102-L110:修改互锁逻辑。仅当 dr16_vt13_ 都已注册时才做互锁;只注册一路时对该路调用 set_timeout_enabled(true),保留失联保护。
  • rmcs_ws/src/rmcs_core/src/hardware/flight.cpp#L88-L89:该组件只调用 register_dr16,属于受影响配置。请在互锁修正后验证 DR16 断线时输出会归零。
  • rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L137-L138:同样只调用 register_dr16,请做同一项断线验证。
  • rmcs_ws/src/rmcs_core/src/hardware/sentry.cpp#L370-L372ChassisBoard 只调用 register_dr16,且哨兵为自主运行平台,失联保护失效的后果更严重,请优先验证。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp` around lines
102 - 110, Update RemoteControl::update_timeout_interlock() in
rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp#L102-L110 so mutual
interlock is applied only when both dr16_ and vt13_ are registered; when only
one is present, call set_timeout_enabled(true) for that device. Validate the
resulting DR16 disconnect behavior in
rmcs_ws/src/rmcs_core/src/hardware/flight.cpp#L88-L89,
rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L137-L138, and
rmcs_ws/src/rmcs_core/src/hardware/sentry.cpp#L370-L372; these sites require no
direct code change.
rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpp-151-168 (1)

151-168: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

外部设定速度有效时 TD 不更新,回退到 TD 时会产生跳变。

第 156 行只有在 reference_velocity 非有限时才调用 td_.update()。当上游持续提供有效的 setpoint_velocity 时,td_ 的内部状态停留在最后一次更新的值。如果上游随后停止提供速度(NaN 或 ready() 变为 false),控制器会立刻回退到陈旧的 TD 状态,reference_anglereference_velocity 会出现阶跃,进而产生扭矩尖峰。

请在每个周期都推进 TD,只在选择参考量时区分两种来源。

🐛 每周期推进 TD
 bool run_joint_servo_(const InputSnapshot& inputs, double& control_torque) {
const auto eso_out = eso_.update(inputs.measurement_angle, last_u_);
- double reference_angle = inputs.setpoint_angle;- double reference_velocity = inputs.setpoint_velocity;- if (!std::isfinite(reference_velocity)) {- const auto td_out = td_.update(inputs.setpoint_angle);- reference_angle = td_out.x1;- reference_velocity = td_out.x2;- }+ const auto td_out = td_.update(inputs.setpoint_angle);++ double reference_angle = td_out.x1;+ double reference_velocity = td_out.x2;+ if (std::isfinite(inputs.setpoint_velocity)) {+ reference_angle = inputs.setpoint_angle;+ reference_velocity = inputs.setpoint_velocity;+ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpp`
around lines 151 - 168, 在 run_joint_servo_ 中每个控制周期都调用
td_.update(inputs.setpoint_angle) 推进 TD 状态;随后仅在选择参考量时,根据 setpoint_velocity
是否有限决定使用外部设定值或 TD 输出,避免回退到过期状态并保持现有扭矩计算流程不变。
rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp-437-452 (1)

437-452: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

GPIO 读取配置在每个控制周期都重新下发。

第 437-452 行位于 command_update() 中。该函数由 SteeringHeroLittleCommand::update() 在每个控制周期调用。因此每个周期都会向板卡发送两条 GPIO 配置命令。

omni_infantry.cpp 第 95 行与 sentry.cpp 第 132 行都把 gpio_digital_read 放在构造函数中,只配置一次。持续重复配置会占用 CAN/串口下行带宽,也可能重置 GPIO 的采样状态。

请把这段配置移到 TopBoard 的构造函数中,紧跟第 317 行的 uart_config 之后。

🐛 移动到构造函数
 board_->start_transmit().uart_config(Spec::kUarts.kUart0, {.baudrate = 921600});
++ board_->start_transmit()+ .gpio_digital_read(+ Spec::kGpios.kUart1Rx,+ {+ .period_ms = 20,+ .pull = librmcs::data::GpioPull::kUp,+ })+ .gpio_digital_read(+ Spec::kGpios.kUart1Tx, {+ .period_ms = 0,+ .asap = false,+ .rising_edge = false,+ .falling_edge = true,+ .capture_timestamp = true,+ .pull = librmcs::data::GpioPull::kUp,+ });

并从 command_update() 中删除第 437-452 行。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp`
around lines 437 - 452, Move the two gpio_digital_read configurations from
command_update() into the TopBoard constructor immediately after the existing
uart_config call, preserving all GPIO settings. Remove the corresponding
per-cycle configuration calls from SteeringHeroLittleCommand::update() while
leaving command_update() behavior otherwise unchanged.
rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp-227-270 (1)

227-270: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

为可变形关节配置 TD 速度/加速度限幅。

deformable_joint_controller.cpptd_max_vel / td_max_acc 默认为无穷大,但 deformable-infantry-omni.yamldeformable-infantry-omni-b.yaml 没有设置这两项;td.hpp 的无穷大可被 fhan 转换为整型 INT_MIN,导致阶跃设定值产生过大 fh 输入。在配置中为每个可变形关节设置有限的 td_max_veltd_max_acc

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp` around
lines 227 - 270, 在 deformable-infantry-omni.yaml 和
deformable-infantry-omni-b.yaml 中,为每个可变形关节补充有限且有效的 td_max_vel 与 td_max_acc
配置值,避免继续使用 deformable_joint_controller.cpp
的无穷大默认值。确保两份配置的所有可变形关节都设置这两个参数,并保持现有关节配置结构不变。
rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp-49-73 (1)

49-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

reset() 会永久丢弃 active_suspension_base_angle 参数值。

构造函数在第 39-42 行从节点参数 active_suspension_base_angle 读取初值,并 clamp 到 [min_angle_ - 5.0, max_angle_]。第 62 行的 reset() 把该成员无条件写成 max_angle_。类中没有任何路径会重新读取参数。因此第一次复位之后,参数配置被永久丢弃,主动悬挂的基准角度只能在 min_angle_max_angle_ 之间切换。

请保存构造时的初值,并在 reset() 中恢复该初值。

🐛 保存并恢复参数初值
 , active_suspension_base_angle_(
std::clamp(
node.get_parameter_or("active_suspension_base_angle", max_angle_),
min_angle_ - 5.0, max_angle_))
+ , active_suspension_base_angle_default_(active_suspension_base_angle_)
, suspension_enable_(node.get_parameter_or("active_suspension_enable", false)) {
 current_target_angle_ = max_angle_;
- active_suspension_base_angle_ = max_angle_;+ active_suspension_base_angle_ = active_suspension_base_angle_default_;

并在成员区新增:

 double active_suspension_base_angle_;
+ double active_suspension_base_angle_default_;
bool suspension_enable_;

注意 active_suspension_base_angle_default_ 的声明必须位于 active_suspension_base_angle_ 之后,以匹配初始化列表顺序。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp` around
lines 49 - 73, Update the deformable-mode class constructor and reset() to
preserve the clamped constructor value of active_suspension_base_angle_. Add
active_suspension_base_angle_default_ immediately after
active_suspension_base_angle_ in the member declarations and initialize it in
matching initialization-list order; have reset() restore
active_suspension_base_angle_ from this saved default instead of max_angle_.
rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp-128-157 (1)

128-157: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

static_assert 固定 VT13 协议包尺寸。

RemoteControlData 混用位域底层类型,sizeof(RemoteControlData)sizeof(RefereeFrameHeader) 可能因实现定义行为偏离实际包长。read_remote_control_data() 依赖 sizeof 读取整个包并计算 CRC16,若尺寸不一致,校验将失败且 VT13::valid() 会一直为 false。在协议结构附近增加尺寸断言,把协议帧长固化。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp` around lines 128 - 157, 在
RemoteControlData 定义附近增加 static_assert,固定其 sizeof(RemoteControlData) 与 VT13
协议实际帧长一致,并同时校验 RefereeFrameHeader 的尺寸;确保 read_remote_control_data() 使用 sizeof
读取和计算 CRC 时不会因位域布局差异产生错误。
rmcs_ws/src/rmcs_core/src/hardware/flight.cpp-80-86 (1)

80-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

为 PX4 串口补充接收路径。

flight.cpp:80-84/px4/serial 注册接口时把 read 固定为返回 0,且 uart_receive_callback 没有消费 Spec::kUarts.kUart0px4_vision_bridge.cpp 只使用 px4_serial_->write 发送 MAVLink。PX4 应答会进入 RX 中断后丢弃,导致心跳/链路应答或协商数据丢失。为 kUart0 增加环形缓冲区,并在 read 中从中弹出数据。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/flight.cpp` around lines 80 - 86, 为 PX4
串口补充接收数据路径:在 flight.cpp 的串口初始化及 uart_receive_callback 中为 Spec::kUarts.kUart0
维护环形缓冲区,将 RX 数据写入该缓冲区;把 px4_serial_->read 从固定返回 0 改为按请求长度弹出并返回缓冲区中的数据,同时保留现有
write 发送逻辑。
rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp-629-660 (1)

629-660: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

核对非转向电机的 CAN id 配置与实际接线。

DjiMotor::generate_command() 的字节位置由 motor.id() 决定:(id - 1) % 4recv_id() 根据该 id 映射到对应的反馈 ID。这些 motor 配置 id 与当前槽位/反馈 ID 映射不一致时,命令会和反馈落到不同的电机。

  • chassis_wheel_motors_[0][1][2][3] 按实际反馈 ID 应为 1、2、3、4;当前配置为 4、1、3、4。
  • chassis_front_climber_motor_ 当前 id 为 1、2;按 0x201/0x203 反馈应为 1、3。
  • chassis_back_climber_motor_ 当前 id 为 2、4;按 0x202/0x204 反馈,若对应电机 id 为 2、4 则配置可自洽;若实际 id 为 3、4,则需要确认并调整。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp`
around lines 629 - 660, 核对并修正该配置块中非转向电机的 CAN ID,使命令槽位与实际反馈 ID 一致:将
chassis_wheel_motors_[0..3] 配置为 1、2、3、4,并将 chassis_front_climber_motor_ 配置为
1、3。确认 chassis_back_climber_motor_[0..1] 的实际接线与反馈 ID;若对应为 3、4,则调整其 ID,否则保持当前 2、4
配置。
rmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpp-637-648 (1)

637-648: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

常驻协程异常后需要重建或持续捕获错误。

CoSchduler::spin_once() 会保存并跳过已完成的异常协程,一旦 spin_context()spin_groups() 抛出,后续调用不会再恢复这两个常驻任务。当前 update() 会打印一次异常日志并释放攀爬状态,但不会重建 spin_context()spin_groups(),导致攀爬逻辑静默终止。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpp` around lines
637 - 648, Update the update() exception path so that after schduler.spin_once()
throws, the persistent spin_context() and spin_groups() tasks are rebuilt or the
scheduler continues capturing and recovering their exceptions; do not leave
either resident coroutine permanently completed. Preserve the existing error
logging, task cancellation, state reset, and release_climber() cleanup.
rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpp-64-65 (1)

64-65: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

避免对字面量使用 bind_directly

bind_directly(false) 会将模板对象绑定为 const T& 参数的地址。第 63 行和第 65 行传入字面量 false 时,解引用会指向临时值并导致悬垂引用;第 65 行新增调用会直接访问解引用结果,风险更大。改用 make_and_bind_directly(false, ...) 或绑定到组件内部/自有存储的 false

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpp`
around lines 64 - 65, Update the shooting logic around single_shoot_.ready() to
avoid calling bind_directly with the false literal; replace it with
make_and_bind_directly(false, ...) or bind to component-owned storage so the
bound value remains valid when dereferenced.
rmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpp-116-116 (1)

116-116: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

需确保发送到 PX4 的视觉时间戳与 EKF2 使用的时间基准一致。

当前 VISION_POSITION_ESTIMATE.usec 直接来自 ROS 消息头时间戳;ROS 时间基准依赖于 NTP/PTP(或 use_sim_time 等配置),与 PX4 的 IMU/EKF 时钟不一定一致。若部署环境未做时间同步或 EV 延迟补偿,PX4 EKF2 可能拒绝外部视觉数据或产生错误融合结果。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpp` at line
116, 更新生成 VISION_POSITION_ESTIMATE.usec 的时间戳逻辑,不要直接使用
sample.stamp.nanoseconds();改用与 PX4 IMU/EKF2 一致的 PX4 单调时钟时间基准,并在必要时应用视觉延迟补偿或
ROS/PX4 时钟偏移,确保发送的时间戳与 EKF2 使用的时钟一致。
rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpp-38-59 (1)

38-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

SteeringWheelController 补齐新增 PID 参数。

SteeringWheelControllerchassis_translation_*/chassis_angular_velocity_**:steering_*/steering_angle_*/wheel_velocity_** 处使用 get_parameter(name).as_double(),而现有 sentry.yaml 只包含这 3 个 chassis_translation_*rmcs_bringup config 中其他 YAML 也没有这些参数的配置块;需要每个加载该组件的配置都补齐这 15 个 PID 键,否则 SentryClimber 相关启动文件会因 ParameterNotDeclaredException 加载失败。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpp`
around lines 38 - 59, 补齐所有加载 SteeringWheelController 的 YAML 配置,为
chassis_translational_velocity_pid_、chassis_angular_velocity_pid_、steering_velocity_pid_、steering_angle_pid_
和 wheel_velocity_pid_ 各添加 kp、ki、kd 三个参数,共 15 个 PID 键;确保 sentry.yaml 及
rmcs_bringup config 中相关配置都包含这些参数,并使用有效的 double 值,避免启动时因参数未声明而失败。
rmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpp-108-108 (1)

108-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

clamp 参数顺序修正正确;但相邻的限位判断条件恒为真。

第 108 行把 std::clamp 的参数顺序改为 (value, lower_limit_, upper_limit_),修正了此前 lo > hi 的未定义行为。这个修改正确。

请同时检查紧随其后的第 112 行至第 113 行:

if (norm_angle(*viewer_control_angle_) >= lower_limit_
|| norm_angle(*viewer_control_angle_) <= upper_limit_) {

lower_limit_ <= upper_limit_ 时,任意数值都至少满足其中一个条件,因此该判断恒为真。结果是 *viewer_delta_angle_by_mouse_wheel_ 每帧都被置 0,该输出接口对下游始终为 0。

从命名判断,意图应为“到达限位时清零增量”。请改为超出范围的判断。

🐛 建议的修复
- if (norm_angle(*viewer_control_angle_) >= lower_limit_- || norm_angle(*viewer_control_angle_) <= upper_limit_) {+ const double normalized = norm_angle(*viewer_control_angle_);+ if (normalized <= lower_limit_ || normalized >= upper_limit_) {
*viewer_delta_angle_by_mouse_wheel_ = 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpp` at line 108,
修正 gimbal viewer 控制逻辑中紧随 std::clamp 的限位判断:将 norm_angle(*viewer_control_angle_) 与
lower_limit_、upper_limit_ 的条件从恒真的或(||)改为表示超出有效范围的判断,并仅在到达限位时将
*viewer_delta_angle_by_mouse_wheel_ 清零;保留正常范围内增量输出。
rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpp-129-141 (1)

129-141: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

kGimbalFree 哨兵值使用 double::min() 并做精确相等比较,属于跨文件契约风险。

std::numeric_limits<double>::min() 是最小正规化正数(约 2.2e-308),不是最小可表示值。第 132 行用 == 精确比较该值。只要上游 /rmcs_navigation/gimbal_toward 的生产者写入的位模式有任何差异(例如经过序列化、单精度转换或使用了 lowest()),该分支就静默失效,云台不会进入自由状态。

请将该哨兵约定提取到共享头文件中,让生产者与消费者引用同一常量;或改用 NaNstd::isnan 判定,与第 136、138 行已有的 isfinite 检查保持一致。

#!/bin/bash# 查找 gimbal_toward 的生产者及其使用的哨兵常量
rg -n -C5 'gimbal_toward' rmcs_ws/src
rg -n -C3 'numeric_limits<double>::min\(\)|kGimbalFree' rmcs_ws/src
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpp` around
lines 129 - 141, Replace the local kGimbalFree sentinel and exact equality
checks in the navigation block of EccentricDualYawController with a shared
sentinel contract, preferably NaN checked via std::isnan consistently with the
existing finite-value handling. Update both the gimbal_toward producer and
consumer to use the same representation, while preserving enter_disabled_state()
when both navigation coordinates indicate free mode.
rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp-27-44 (1)

27-44: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

把输入、输出和内部状态成员移到 private 段。

第 20 行的 public: 一直延续到第 129 行。因此 sentry_events_requests_cached_events_last_fb_posture_ 等全部对外暴露。这些是实现细节。请把它们移到 private: 段,只保留构造函数、before_updating()update() 为公开。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp`
around lines 27 - 44, 将 SentryDecision 类中当前由 public 继承的输入、输出及内部状态成员移入 private:
段,包括 sentry_events_、requests_、cached_events_ 和 last_fb_posture_
等;仅保留构造函数、before_updating() 与 update() 位于 public: 段。
rmcs_ws/src/rmcs_core/src/referee/status/field.hpp-144-148 (1)

144-148: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

姿态反馈位宽与姿态命令枚举取值范围不匹配。SentryInfo::posture 定义为 2 位(取值 0-3),而 SentryCommand::Posture 定义了 7 个取值(0-6)。两者在反馈校验中被直接比较,因此所有 POWERED_* 姿态请求都无法确认完成,会永久停留在请求集合中并阻塞后续命令。

  • rmcs_ws/src/rmcs_core/src/referee/status/field.hpp#L144-L148:核对裁判协议手册中 posture 字段的实际位宽。如果是 3 位,把 std::uint16_t posture : 2 改为 : 3,并把同组的 energy_core_activatableis_powered 之后的位分配同步调整,保持 static_assert(sizeof(SentryInfo) == 14) 成立。
  • rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp#L210-L217:在位宽修正后,确认 static_cast<uint8_t>(it->second) == fb_posture_id 能匹配 POWERED_ATTACKPOWERED_DEFENSEPOWERED_MOVE。如果协议确实只提供 2 位反馈,请为这三个事件改用其他完成判据(例如结合 is_powered 位),并增加超时清除以避免请求永久驻留。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/referee/status/field.hpp` around lines 144 - 148,
修正 SentryInfo::posture 与 SentryCommand::Posture 的反馈取值范围不匹配:在
rmcs_ws/src/rmcs_core/src/referee/status/field.hpp:144-148 核对协议位宽,若为 3 位则将
posture 扩展为 3 位并同步调整后续字段,保持 static_assert(sizeof(SentryInfo) == 14);在
rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp:210-217
确认反馈比较可匹配 POWERED_ATTACK、POWERED_DEFENSE 和 POWERED_MOVE,若协议仅支持 2 位则改用包含
is_powered 的完成判据并增加超时清除,避免请求永久驻留。
rmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpp-220-226 (1)

220-226: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

pitch_gravity_feedforward() 在保持模式下恒返回 0。

第 221 行在 ctrl_hold_active_ 为真时提前返回 0.0。第 278 行只在保持模式下调用该函数,此时 ctrl_hold_active_ 一定为真。因此保持模式的重力前馈永远是 0。第 92 行的调用只发生在 !ctrl_hold_active_ 分支,该早退分支也永远不会命中。

保持模式需要重力补偿来维持目标角度。请移除该早退,或明确说明保持模式不使用前馈并删除第 278 行的加项。

🐛 建议修复
 auto pitch_gravity_feedforward() const -> double {
- if (ctrl_hold_active_)- return 0.0;
if (!input_.pitch_angle.ready() || !std::isfinite(*input_.pitch_angle))
return 0.0;
return pitch_gravity_ff_gain_ * std::sin(*input_.pitch_angle - pitch_gravity_ff_phase_);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpp`
around lines 220 - 226, 移除 pitch_gravity_feedforward() 中基于 ctrl_hold_active_
的提前返回,使保持模式调用该函数时仍能计算重力补偿;保留角度输入有效性检查及现有前馈计算逻辑。
rmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpp-49-59 (1)

49-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

valid_last_remote_control_received_at_ 存在跨线程数据竞争。

store_status() 由板卡的串口接收线程调用,它写入 last_remote_control_received_at_(第 50 行)和 valid_(第 51 行)。update_status() 由执行器线程调用,它读取这两个成员,并在 refresh_validity() 中写入 valid_ = false(第 199 行)。

这两个成员不是原子的。TimePoint 是 8 字节以上的对象,非原子读写属于未定义行为。相比之下,data_part1_data_part2_data_part3_ 都使用了 std::atomic,说明这里确实是双线程访问。

请把这两个字段改为原子类型。

🛡️ 建议修改
- TimePoint last_remote_control_received_at_ = TimePoint::min();- bool valid_ = false;+ std::atomic<TimePoint::rep> last_remote_control_received_at_{TimePoint::min()+ .time_since_epoch()+ .count()};+ std::atomic<bool> valid_{false};
bool timeout_enabled_ = true;

对应地,store_status() 使用 store(..., std::memory_order::relaxed)refresh_validity() 使用 load(..., std::memory_order::relaxed) 并把 rep 还原为 TimePoint

Also applies to: 194-200, 281-283

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpp` around lines 49 - 59, 将
Device 中的 valid_ 和 last_remote_control_received_at_ 改为可安全跨线程访问的原子类型,并更新
store_status()、update_status()、refresh_validity() 及相关读取逻辑以使用 relaxed
load/store;对原子时间值读取后还原为 TimePoint,保持现有有效性判断和状态更新行为不变。
rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp-183-189 (1)

183-189: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

detect_friction_faulty() 现在恒返回 false,卡弹检测被完全禁用。

循环命中故障条件后返回 false,函数末尾也返回 false。因此 update_friction_status() 永远不会进入故障分支,/gimbal/friction_jammed 永远为 false,摩擦轮堵转时不会自动停机。

如果目的是临时关闭该检测,请直接让函数返回 false 并加注释说明;如果目的是保留检测,请恢复 return true

🐛 恢复故障检测的修改
 bool detect_friction_faulty() {
for (size_t i = 0; i < friction_count_; i++) {
if (abs(*friction_velocities_[i]) < abs(*friction_control_velocities_[i] * 0.5))
- return false;+ return true;
}
return false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp`
around lines 183 - 189, 修复 detect_friction_faulty()
的返回逻辑:当任一摩擦轮满足速度低于控制速度一半的故障条件时返回 true,循环结束仍返回 false。确保 update_friction_status()
能进入故障分支并正确报告摩擦轮堵转。
rmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpp-142-171 (1)

142-171: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

id_ 为 0 时,recv_id() 会返回发送帧的 ID。

recv_id(Type::kM3508, 0) 返回 0x200recv_id(Type::kGM6020, 0) 返回 0x2040x200 是 DJI 电调的指令帧 ID,不是反馈帧 ID。如果某个未配置 ID 的电机调用 match_then_store_status(),它会把回环的指令帧当作反馈数据存入,导致角度、速度和力矩全部错误。

请让 ID 为 0 时返回一个不可能匹配的值,并让 match_then_store_status() 直接拒绝。

🛡️ 建议修改
 static constexpr auto recv_id(Type type, std::uint8_t index) -> std::uint32_t {
+ if (index == 0)+ return 0;
switch (type) {
case Type::kGM6020:
case Type::kGM6020Voltage: return 0x204 + index;
case Type::kM3508:
case Type::kM2006: return 0x200 + index;
}
return 0;
}
 bool match_then_store_status(std::uint32_t can_id, std::span<const std::byte> can_data) {
- if (can_id != recv_id())+ const auto expected = recv_id();+ if (expected == 0 || can_id != expected)
return false;
store_status(can_data);
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpp` around lines 142 -
171, 更新静态 recv_id(Type, index) 与 match_then_store_status:当 index/id_ 为 0
时返回不可能匹配的接收 ID,并让 match_then_store_status 在 id_ 为 0 时立即返回 false,不调用
store_status;其他已配置 ID 的匹配与存储行为保持不变。
rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp-150-154 (1)

150-154: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

底盘轮方向配置需要同步更新。

chassis_wheel_motors_ 当前未配置 set_reversed(),但 DjiMotor 的速度和力矩换算方向由 reversed 控制,DeformableOmniWheelController 仍按之前的底盘坐标约定使用轮速度输入/力矩输出。如果硬件或本体方向改为默认正号,底盘控制方向会整体反转;需要补齐方向配置,或在 deformable_omni_wheel_controller 与硬件接口中同步符号约定。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp` around lines
150 - 154, 同步底盘轮方向配置:在 chassis_wheel_motors_ 的 DjiMotor::Config 链中补充明确的
set_reversed() 设置,使 DjiMotor 的速度和力矩符号继续匹配 DeformableOmniWheelController
既有底盘坐标约定;根据实际硬件安装方向使用正确的反向值,不要依赖默认方向。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aed7a725-73ba-4206-afed-f49cbbd357be

📥 Commits

Reviewing files that changed from the base of the PR and between 0a494d4 and 6686325.

📒 Files selected for processing (96)
  • .github/workflows/update-image.yml
  • .gitmodules
  • .script/autoaim-debug
  • .script/complete/_local-context
  • .script/complete/_remote-context
  • .script/host/rmcs
  • .script/identification/fit_friction_velocity_pid.py
  • .script/identification/fit_gravity_torque.py
  • .script/identification/fit_sweep_graybox.py
  • .script/local-context
  • .script/remote-context
  • .script/remote-status
  • .script/rmcs-cli
  • .script/scan-remote
  • Dockerfile
  • rmcs_ws/src/rmcs_auto_aim_v2
  • rmcs_ws/src/rmcs_bringup/config/auto_aim_test.yaml
  • rmcs_ws/src/rmcs_bringup/config/deformable-infantry-omni-b.yaml
  • rmcs_ws/src/rmcs_bringup/config/deformable-infantry-omni.yaml
  • rmcs_ws/src/rmcs_bringup/config/flight.yaml
  • rmcs_ws/src/rmcs_bringup/config/navigation_test.yaml
  • rmcs_ws/src/rmcs_bringup/config/sentry.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-hero-little-six-friction.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-hero-little.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-infantry.yaml
  • rmcs_ws/src/rmcs_core/CMakeLists.txt
  • rmcs_ws/src/rmcs_core/package.xml
  • rmcs_ws/src/rmcs_core/plugins.xml
  • rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_climber_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/climber/stick_group.hpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/climber/track_group.hpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_chassis.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_layer.hpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_omni_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_suspension.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw_solver.hpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpp
  • rmcs_ws/src/rmcs_core/src/controller/gimbal/two_axis_gimbal_solver.hpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/hero_heat_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/putter_controller.cpp
  • rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp
  • rmcs_ws/src/rmcs_core/src/debug/value_collector.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-steering.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpp
  • rmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpp
  • rmcs_ws/src/rmcs_core/src/hardware/device/lk_motor.hpp
  • rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp
  • rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp
  • rmcs_ws/src/rmcs_core/src/hardware/flight.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/sentry.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/steering-infantry.cpp
  • rmcs_ws/src/rmcs_core/src/hardware/util/status_monitor.hpp
  • rmcs_ws/src/rmcs_core/src/identification/static_torque_test_controller.cpp
  • rmcs_ws/src/rmcs_core/src/identification/swept_frequency_controller.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/deformable_infantry_ui.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/hero.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/infantry.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/shape/shape.hpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/widget/animated_toggle.hpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/widget/crosshair_circle.hpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/widget/deformable_chassis_top_view.hpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/widget/status_ring.hpp
  • rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp
  • rmcs_ws/src/rmcs_core/src/referee/status.cpp
  • rmcs_ws/src/rmcs_core/src/referee/status/field.hpp
  • rmcs_ws/src/rmcs_msgs/include/rmcs_msgs/chassis_mode.hpp
  • rmcs_ws/src/rmcs_msgs/include/rmcs_msgs/rmcs_msgs.hpp
  • rmcs_ws/src/rmcs_msgs/include/rmcs_msgs/robot_id.hpp
  • rmcs_ws/src/rmcs_msgs/include/rmcs_msgs/sentry_event.hpp
  • rmcs_ws/src/rmcs_utility/include/rmcs_utility/csv_writer.hpp
  • rmcs_ws/src/rmcs_utility/include/rmcs_utility/rclcpp/node_mixin.hpp
  • rmcs_ws/src/rmcs_utility/include/rmcs_utility/ring_buffer.hpp
💤 Files with no reviewable changes (7)
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_layer.hpp
  • rmcs_ws/src/rmcs_bringup/config/steering-infantry.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-hero-little.yaml
  • rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-steering.cpp
  • rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_wheel_controller.cpp
  • rmcs_ws/src/rmcs_core/src/referee/app/ui/shape/shape.hpp

Comment thread.script/rmcs-cli
Comment on lines +137 to +140
subprocess.run([
"tmux", "send-keys", "-t", _pane_control,
f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter"
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

__file__ 可能是相对路径,cd 之后重启会失败。

用户以 .script/rmcs-cli 这类相对路径调用脚本时,__file__ 为相对路径。发送到窗格的命令先执行 cd {RMCS_PATH},再执行 {__file__}。若当前工作目录不是 RMCS_PATH,控制面板不会启动,窗格只显示 “command not found”。

请使用绝对路径。

🐛 建议的修复
+ script_path = os.path.abspath(__file__)
subprocess.run([
"tmux", "send-keys", "-t", _pane_control,
- f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter"+ f"cd {RMCS_PATH} && RMCS_CLI=1 {script_path}", "Enter"
])
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
subprocess.run([
"tmux", "send-keys", "-t", _pane_control,
f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter"
])
script_path = os.path.abspath(__file__)
subprocess.run([
"tmux", "send-keys", "-t", _pane_control,
f"cd {RMCS_PATH} && RMCS_CLI=1 {script_path}", "Enter"
])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/rmcs-cli around lines 137 - 140, Update the tmux restart command
around __file__ to resolve the script path to an absolute path before
constructing the command sent by subprocess.run. Preserve the existing RMCS_PATH
change-directory and environment-variable behavior while ensuring restarting
works when the script was invoked via a relative path such as .script/rmcs-cli.

Comment thread.script/scan-remote
Comment on lines 60 to +85
@staticmethod
def read_key():
if not RawTerminal.key_ready(0.02):
return None
raw = sys.stdin.buffer.raw.read(1)
if raw == b"\x03":
raise KeyboardInterrupt
if raw in (b"\r", b"\n"):
return "enter"
if raw == b"\t":
return "next"
if raw in (b"w", b"W"):
return "prev"
if raw in (b"s", b"S"):
return "next"
if raw == b"\x0e":
return "next"
if raw == b"\x10":
return "prev"
if raw in (b"\x7f", b"\x08"):
return "backspace"
if raw == b"\x1b":
if not RawTerminal.key_ready(0.02):
return "quit"
nxt = sys.stdin.buffer.raw.read(1)
if nxt != b"[":
return "escape"
if not RawTerminal.key_ready(0.02):
return "escape"
direction = sys.stdin.buffer.raw.read(1)
if direction == b"A":
return "prev"
if direction == b"B":
return "next"
if direction == b"Z":
return "prev"
return "escape"
if raw in (b"q", b"Q"):
return "quit"
os._exit(130)
if raw in RawTerminal._SIMPLE_KEYS:
return RawTerminal._SIMPLE_KEYS[raw]
if raw == b"\x1b":
return RawTerminal._read_escape()
if len(raw) == 1 and 32 <= raw[0] <= 126:
return raw.decode()
return None

@staticmethod
def _read_escape():
if not RawTerminal.key_ready(0.02):
os._exit(130)
nxt = sys.stdin.buffer.raw.read(1)
if nxt != b"[" or not RawTerminal.key_ready(0.02):
os._exit(130)
direction = sys.stdin.buffer.raw.read(1)
if direction in RawTerminal._ESCAPE_DIRS:
return RawTerminal._ESCAPE_DIRS[direction]
os._exit(130)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

os._exit(130) 会跳过终端恢复,退出后用户终端保持 raw 模式。

RawTerminal.__enter__ 关闭了 ECHOICANON(第 43 行)。os._exit 立即终止进程,不执行 __exit__finallyatexit。因此第 66、78、81、85 行的每一条退出路径都会让调用者的终端停留在无回显、无行编辑的状态。用户必须手动执行 stty sane 才能恢复。

第二个问题在 _read_escape。任何以 ESC 开头但不是 ESC [ A/B/Z 的序列都会触发退出,包括 Alt+字母HomeEndF1~F4 以及 ESC [ C/D(左右方向键)。用户按方向键左右时程序会直接终止。

建议改为抛出自定义异常,由 RawTerminal 上下文管理器恢复终端后再 sys.exit(130);未识别的转义序列应返回 None 而不是退出。

🐛 建议的修复
+class UserAbort(Exception):+ pass++
class RawTerminal:
 `@staticmethod`
def read_key():
if not RawTerminal.key_ready(0.02):
return None
raw = sys.stdin.buffer.raw.read(1)
if raw in (b"q", b"Q"):
- os._exit(130)+ raise UserAbort
if raw in RawTerminal._SIMPLE_KEYS:
return RawTerminal._SIMPLE_KEYS[raw]
if raw == b"\x1b":
return RawTerminal._read_escape()
if len(raw) == 1 and 32 <= raw[0] <= 126:
return raw.decode()
return None
`@staticmethod`
def _read_escape():
if not RawTerminal.key_ready(0.02):
- os._exit(130)+ raise UserAbort
nxt = sys.stdin.buffer.raw.read(1)
- if nxt != b"[" or not RawTerminal.key_ready(0.02):- os._exit(130)+ if nxt != b"[":+ return None+ if not RawTerminal.key_ready(0.02):+ return None
direction = sys.stdin.buffer.raw.read(1)
- if direction in RawTerminal._ESCAPE_DIRS:- return RawTerminal._ESCAPE_DIRS[direction]- os._exit(130)+ return RawTerminal._ESCAPE_DIRS.get(direction)

main 的顶层捕获 UserAbortsys.exit(130)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/scan-remote around lines 60 - 85, Replace every os._exit(130) path
in RawTerminal.read_key and RawTerminal._read_escape with the custom UserAbort
exception so the RawTerminal context manager can restore terminal settings;
catch UserAbort at main’s top level and exit with status 130. Make _read_escape
return None for unrecognized or incomplete escape sequences, including Alt-key,
Home/End, function-key, and left/right-arrow sequences, while preserving
recognized escape-key mappings.

Comment thread.script/scan-remote
Comment on lines 184 to +190
def expanded_networks(interface):
ip = interface.ip
if ip.packed[0] == 192 and ip.packed[1] == 168:
return [
ipaddress.ip_network(f"192.168.{segment}.0/24")
for segment in DEFAULT_192_168_SEGMENTS
]

return [ipaddress.ip_network(f"{ip}/24", strict=False)]
prefix = ".".join(str(part) for part in ip.packed[:2])
return [
ipaddress.ip_network(f"{prefix}.{segment}.0/24")
for segment in DEFAULT_SCAN_SEGMENTS
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

expanded_networks 不再包含接口自身所在的网段。

新实现只取 IP 的前两个字节,然后固定生成 x.y.1.0/24x.y.5.0/24。接口自身的第三段被丢弃。

举例:接口地址为 10.20.30.40/24 时,返回 10.20.1.0/2410.20.5.0/24,其中不包含 10.20.30.0/24。同网段的远端主机因此扫不到。地址为 192.168.100.5 时同样失效。

请始终把接口所在网络加入结果。

🐛 建议的修复
 def expanded_networks(interface):
ip = interface.ip
prefix = ".".join(str(part) for part in ip.packed[:2])
- return [- ipaddress.ip_network(f"{prefix}.{segment}.0/24")- for segment in DEFAULT_SCAN_SEGMENTS- ]+ networks = [ipaddress.ip_network(f"{ip}/24", strict=False)]+ networks.extend(+ ipaddress.ip_network(f"{prefix}.{segment}.0/24")+ for segment in DEFAULT_SCAN_SEGMENTS+ )+ return networks
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def expanded_networks(interface):
ip = interface.ip
if ip.packed[0] == 192 and ip.packed[1] == 168:
return [
ipaddress.ip_network(f"192.168.{segment}.0/24")
for segment in DEFAULT_192_168_SEGMENTS
]
return [ipaddress.ip_network(f"{ip}/24", strict=False)]
prefix = ".".join(str(part) for part in ip.packed[:2])
return [
ipaddress.ip_network(f"{prefix}.{segment}.0/24")
for segment in DEFAULT_SCAN_SEGMENTS
]
def expanded_networks(interface):
ip = interface.ip
prefix = ".".join(str(part) for part in ip.packed[:2])
networks = [ipaddress.ip_network(f"{ip}/24", strict=False)]
networks.extend(
ipaddress.ip_network(f"{prefix}.{segment}.0/24")
for segment in DEFAULT_SCAN_SEGMENTS
)
return networks
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/scan-remote around lines 184 - 190, 更新 expanded_networks,使返回结果始终包含
interface.ip 所在的实际网络,并保留 DEFAULT_SCAN_SEGMENTS 生成的扫描网段;使用接口地址的网络前缀和 prefixlen
计算该网段,而不是仅根据 IP 前两段构造结果。

Comment on lines +87 to +96
if (tick_++ % write_interval_ != 0)
return;

csv_file_ << sample_count_++;
for (const auto& unit : units_)
csv_file_ << "," << *unit->value;
csv_file_ << "\n";

if (sample_count_ % flush_interval_ == 0)
csv_file_.flush();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

write_interval_flush_interval_ 为 0 时会触发除零。

这两个值来自参数,没有做校验。整数取模运算中除数为 0 是未定义行为,在 x86 上会产生 SIGFPE 并导致进程崩溃。负值也会让取模结果为负,从而永远不等于 0,采样与刷新都会停止。

另外 tick_sample_count_int。以 1 kHz 运行约 25 天后会发生有符号整数溢出,属于未定义行为。请改为 std::int64_t

🛡️ 建议修改
 node::param("write_interval", write_interval_);
node::param("flush_interval", flush_interval_);
++ if (write_interval_ < 1) {+ node::error("write_interval must be >= 1, got {}; fallback to 1", write_interval_);+ write_interval_ = 1;+ }+ if (flush_interval_ < 1) {+ node::error("flush_interval must be >= 1, got {}; fallback to 1", flush_interval_);+ flush_interval_ = 1;+ }
- int tick_ = 0;- int sample_count_ = 0;+ std::int64_t tick_ = 0;+ std::int64_t sample_count_ = 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/debug/value_collector.cpp` around lines 87 - 96, 在
value collector 的参数初始化或构造流程中校验 write_interval_ 和 flush_interval_
必须为正值,避免后续取模除零及负值导致采样、刷新停止;同时将 tick_ 和 sample_count_ 的类型改为
std::int64_t,并确保相关声明和使用保持一致。

DeformableInfantryOmni& status_;
Component& command_;

std::unique_ptr<librmcs::board::RmcsBoardLite> board_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

两个 BottomBoard 都把 board_ 声明在设备成员之前,销毁顺序不安全。 共同根因是成员声明顺序:C++ 按声明的逆序销毁成员,因此 board_ 会在 dr16_supercap_、各电机和 monitor_ 之后销毁。librmcs::board::RmcsBoardLite 在内部线程上分发接收回调,在 board_ 析构停止该线程之前,这些成员已被销毁,回调会访问悬垂对象。两个文件中的 TopBoard 都把 board_ 声明为最后一个成员,顺序是正确的。

  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp#L373-L373: 把 std::unique_ptr<librmcs::board::RmcsBoardLite> board_; 从此处移到 BottomBoard 的最后一个成员位置,即 StatusMonitor monitor_{}; 之后。
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpp#L563-L563: 做同样的移动,把 board_ 放到 StatusMonitor monitor_{}; 之后。
📍 Affects 2 files
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp#L373-L373 (this comment)
  • rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpp#L563-L563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp` at line 373,
在 deformable-infantry-omni.cpp 的 BottomBoard 中,将 board_ 移到 StatusMonitor
monitor_{} 之后;在 deformable-infantry-omni-b.cpp 的 BottomBoard
中执行相同调整(分别位于指定行范围)。保持其他成员顺序不变,使 board_ 成为最后声明的成员。

Comment on lines +27 to +32
enum class ModeSwitch : uint8_t {
kUnknown = 0,
kCine = 1,
kNormal = 2,
kSport = 3,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

mode_switch + 1 会产生越界的枚举值。

data.mode_switch 是 2 位位域,取值范围是 0..3。第 203 行加 1 后得到 1..4。ModeSwitch 只定义到 3(kSport)。当原始值为 3 时,mode_switch_ 变成 4。

RemoteControl::select_control_source()switch 对值 4 不匹配任何 case,会走到 break,然后回退到 DR16。结果是遥控源仲裁在该输入下静默失效,且没有任何告警。

请确认协议中 mode_switch 原始值到模式的映射,并对未知值做显式处理。

🐛 显式映射并处理未知值
- mode_switch_ = static_cast<ModeSwitch>(data.mode_switch + 1);+ switch (data.mode_switch) {+ case 0: mode_switch_ = ModeSwitch::kCine; break;+ case 1: mode_switch_ = ModeSwitch::kNormal; break;+ case 2: mode_switch_ = ModeSwitch::kSport; break;+ default: mode_switch_ = ModeSwitch::kUnknown; break;+ }

请按实际协议调整 case 的对应关系。

Also applies to: 202-203

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp` around lines 27 - 32, 修正
RemoteControl 中 data.mode_switch 到 ModeSwitch 的映射,避免对 2 位原始值直接执行 +1 导致值
4;依据实际协议为 0..3 显式映射到对应枚举,并在未知或未定义值时进行显式处理,确保 select_control_source() 不会静默回退到
DR16。

Comment on lines +455 to 493
void can_receive_callback(const Spec::Can& can, const View::Can& data) override {
if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
return;
auto can_id = data.can_id;
// can2_receive_rate_counter_.record(can_id);
if (can_id == 0x143) {
gimbal_player_viewer_motor_.store_status(data.can_data);
if (can == Spec::kCans.kCan0) {
// can0_receive_rate_counter_.record(can_id);
can0_detect[can_id - 0x141] = 1;
if (can_id == 0x141) {
gimbal_top_yaw_motor_.store_status(data.can_data);
} else if (can_id == 0x143) {
gimbal_pitch_motor_.store_status(data.can_data);
} else if (can_id == 0x142) {
gimbal_bullet_feeder_.store_status(data.can_data);
}
} else if (can == Spec::kCans.kCan1) {
// can1_receive_rate_counter_.record(can_id);
friciton_detect[can_id - 0x201] = 1;
if (can_id == 0x201) {
gimbal_friction_wheels_[0].store_status(data.can_data);
} else if (can_id == 0x202) {
gimbal_friction_wheels_[1].store_status(data.can_data);
} else if (can_id == 0x203) {
gimbal_friction_wheels_[2].store_status(data.can_data);
} else if (can_id == 0x204) {
gimbal_friction_wheels_[3].store_status(data.can_data);
}
} else if (can == Spec::kCans.kCan2) {
// can2_receive_rate_counter_.record(can_id);
if (can_id == 0x203) {
putter_motor_.store_status(data.can_data);
} else if (can_id == 0x201) {
gimbal_friction_wheels_[4].store_status(data.can_data);
friciton_detect[4] = 1;
} else if (can_id == 0x202) {
gimbal_friction_wheels_[5].store_status(data.can_data);
friciton_detect[5] = 1;
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

friciton_detectcan0_detect 未初始化,并且索引计算会越界写入。

这里有两个独立的内存安全缺陷。

第一,第 547-548 行声明 int friciton_detect[6];int can0_detect[3];TopBoard 的构造函数初始化列表没有初始化它们,声明处也没有初始化器。第 360 行与第 366 行读取这些元素,读到的是不确定值。对比第 910 行的 int check[10] = {0};,那里做了初始化。

第二,第 461 行与第 471 行在检查 can_id 之前就用它计算索引:

  • can0_detect[can_id - 0x141]:数组只有 3 个元素,只有 can_id 为 0x141/0x142/0x143 时索引才合法。Can0 上任何其他 ID 都会越界写。例如 can_id 为 0x201 时索引是 192。
  • friciton_detect[can_id - 0x201]:数组只有 6 个元素。Can1 上收到 0x1FF 或 0x300 时索引分别是 -2 和 255。

同样的模式出现在第 835 行与第 848 行的 check[can_id - 0x201]。虽然 check 有 10 个元素且已初始化,越界范围小一些,但缺陷性质相同。

请初始化数组,并在写入前做范围检查。

🐛 初始化数组并增加范围检查
 int count_ = 0;
- int friciton_detect[6];- int can0_detect[3];+ int friction_detect[6] = {0};+ int can0_detect[3] = {0};
 if (can == Spec::kCans.kCan0) {
- can0_detect[can_id - 0x141] = 1;+ if (can_id >= 0x141 && can_id <= 0x143)+ can0_detect[can_id - 0x141] = 1;
if (can_id == 0x141) {
 } else if (can == Spec::kCans.kCan1) {
- friciton_detect[can_id - 0x201] = 1;+ if (can_id >= 0x201 && can_id <= 0x206)+ friction_detect[can_id - 0x201] = 1;
if (can_id == 0x201) {

BottomBoard 的第 835 行与第 848 行请做同样的范围检查。同时请修正 friciton_detect 的拼写为 friction_detect,并同步第 360、364、487、490 行的引用。

Also applies to: 546-548

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp`
around lines 455 - 493, Initialize TopBoard’s friciton_detect and can0_detect
arrays to zero at declaration or in the constructor, then rename friciton_detect
to friction_detect and update all references including the cited status checks
and callbacks. In can_receive_callback, validate each CAN ID before computing an
array index: restrict can0_detect to 0x141–0x143 and friction_detect to
0x201–0x206, while preserving motor status handling. Apply equivalent bounds
checks before check[can_id - 0x201] writes in BottomBoard.

Comment on lines +229 to +231
} else {
to_erase.push_back(event);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

verify_feedbackelse 分支会擦除本帧未被下发的请求。

consume_one_event() 每帧只下发一个最高优先级请求(第 193 行 break)。随后 verify_feedback() 遍历整个requests_ 集合,第 229-231 行把所有 EXCHANGE_AMMO_SUPPLY_POINTEXCHANGE_AMMO_REMOTEEXCHANGE_HP_REMOTE 无条件加入擦除列表。

结果:如果同一帧内 requests_ 中存在多个此类事件,只有优先级最高的那个被写入 sentry_decision_field_,其余的在从未下发的情况下被丢弃。

此外,即便只有一个请求,它也只在单帧内出现于输出字段。裁判系统交互命令有发送频率上限,下游发送模块很可能采样不到这个单帧脉冲。

请只擦除本帧实际被消费的那个事件,并保持请求存在直到确认已发送。

🐛 建议修复方向
- auto update() -> void override {+ auto update() -> void override {
...
- consume_one_event();+ last_consumed_event_ = consume_one_event();
verify_feedback();
}
 } else {
- to_erase.push_back(event);+ // 只擦除本帧实际下发的一次性请求。+ if (last_consumed_event_ && *last_consumed_event_ == event)+ to_erase.push_back(event);
}

consume_one_event() 需要返回 std::optional<SentryEvent>

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp`
around lines 229 - 231, 修改 consume_one_event() 使其返回
std::optional<SentryEvent>,仅在实际选中并写入 sentry_decision_field_ 的事件被消费时返回该事件;更新
verify_feedback() 只将这个已消费事件加入 to_erase,并保留其他 requests_ 请求,直到后续确认发送。

@github-project-automationgithub-project-automationBot moved this from Todo to Done in RMCSAug 10, 2026
@coderabbitaicoderabbitaiBot mentioned this pull request Aug 13, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants

@creeper5820@qzhhhi@dwx5@ZGZ713912@floatpigeon@noskillzheng@zlq04222