diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md
index 8b080bae7be..1059f3bd1e5 100644
--- a/docs/en/SUMMARY.md
+++ b/docs/en/SUMMARY.md
@@ -122,6 +122,7 @@
- [Flight Termination Configuration](advanced_config/flight_termination.md)
- [First Flight Guidelines](flying/first_flight_guidelines.md)
- [Flying](flying/index.md)
+ - [Mode Requirements](flight_modes/mode_requirements.md)
- [Missions](flying/missions.md)
- [Flight Reporting](getting_started/flight_reporting.md)
- [Flight Log Analysis](log/flight_log_analysis.md)
diff --git a/docs/en/advanced_features/precland.md b/docs/en/advanced_features/precland.md
index 305eeb09234..f68b8f2c7f0 100644
--- a/docs/en/advanced_features/precland.md
+++ b/docs/en/advanced_features/precland.md
@@ -110,6 +110,20 @@ At time of writing is no _convenient_ way to directly invoke precision landing (
- [MAV_CMD_DO_SET_MODE](https://mavlink.io/en/messages/common.html#MAV_CMD_DO_SET_MODE) should work, but you will need to determine the appropriate base and custom modes used by PX4 to represent the precision landing mode.
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
## Hardware Setup
### IR Sensor/Beacon Setup
diff --git a/docs/en/flight_modes/mode_requirements.md b/docs/en/flight_modes/mode_requirements.md
new file mode 100644
index 00000000000..45e8aa8fc46
--- /dev/null
+++ b/docs/en/flight_modes/mode_requirements.md
@@ -0,0 +1,295 @@
+# Mode Requirements
+
+::: info
+This documentation was auto-generated from the source code (see [docs/scripts/get_mode_requirements](https://github.com/PX4/PX4-Autopilot/tree/main/docs/scripts/get_mode_requirements)).
+:::
+
+Mode requirements define the set of conditions that must be met in order to arm in a particular flight mode, or to switch to the mode if it is already armed.
+
+Requirements are defined for internal modes in [mode_requirements.cpp](https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/commander/ModeUtil/mode_requirements.cpp), and for ROS 2 external modes in [requirement_flags.hpp](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/px4_ros2_cpp/include/px4_ros2/common/requirement_flags.hpp) (Github `Auterion/px4-ros2-interface-lib` repository).
+The mode requirements are the same in both cases.
+
+The following sections provide an overview of the requirements and what modes they are used in.
+
+## Requirements Definitions
+
+| Requirement | Example |
+| --- | --- |
+| `mode_req_angular_velocity` | Angular velocity (gyroscope) |
+| `mode_req_attitude` | Attitude/pose (IMU, or theoretically a motion capture system) |
+| `mode_req_local_position` | Position relative to EKF2 origin ('0') point (GNSS, VIO, mocap) |
+| `mode_req_local_position_relaxed` | Position relative to EKF2 origin ('0') point but accepts poor accuracy (Optical flow)
Typically optical flow. You set zero when you take off and just integrate up the flow. So the absolute position can be very inaccurate but better than nothing. Useful to not drift away quickly and roughly know where you are. |
+| `mode_req_global_position` | Position measurement updates in a global coordinate frame (GNSS, or local position and global reference to EKF 0) |
+| `mode_req_global_position_relaxed` | Position measurement updates in a global coordinate frame but accepts poor accuracy
Local position and global reference to EKF2 origin |
+| `mode_req_local_alt` | Local altitude relative to EKF2 origin ('0') position (Barometer corrected by GNSS altitude over time, distance sensor)
Usually not the distance sensor because if the ground shifts then the coordinate frame would shift with it (range aid problem). |
+| `mode_req_mission` | Valid mission in autopilot's storage (Mission mode only requirement)
can be from last time no need to upload fresh |
+| `mode_req_offboard_signal` | Offboard heartbeat
MAVLink messages SET_ATTITUDE_TARGET or SET_POSITION_TARGET_LOCAL_NED or SET_POSITION_TARGET_GLOBAL_INT not timing out. Offboard mode specific requirement. |
+| `mode_req_home_position` | Global home reference must be set
Specific requirement for Return mode |
+| `mode_req_wind_and_flight_time_compliance` | Safety compliance limits on wind and flight time.
Blocks arming or switching into the mode when the estimated wind speed exceeds `COM_WIND_MAX` (if `COM_WIND_MAX_ACT` is set to an action stronger than a warning), or when the flight time exceeds `COM_FLT_TIME_MAX`. Both limits are disabled by default. Modes commanded by a failsafe are exempt (see also `COM_WIND_WARN`). |
+| `mode_req_prevent_arming` | Mode prevents arming
The vehicle cannot be armed while this mode is selected. Set for modes such as Land, Orbit, and Return that you can't take off in (they can only be entered in flight). |
+| `mode_req_manual_control` | Requires stick input
Manual control can come from an RC system (RC driver -> channel mapping/calibration -> [`manual_control_input`](../msg_docs/ManualControlSetpoint.md)) or from a joystick (MAVLink [MANUAL_CONTROL](https://mavlink.io/en/messages/common.html#MANUAL_CONTROL) message -> `manual_control_input`). A selector configured with [COM_RC_IN_MODE](../advanced_config/parameter_reference.md#COM_RC_IN_MODE) chooses which source is used and publishes it as `manual_control_setpoint`. The requirement is met while the selected source has not timed out. |
+| `mode_req_other` | Others requirement.
This is used by external modes. It is intended to specify additional requirements not covered by the existing flags |
+
+### Naming Conventions
+
+In general requirement flag names are abstracted from specific sensors.
+This is done because particular requirements can often be met by several sensors.
+For example, GNSS is the most common source of global position, but it isn't the only one.
+
+The requirements include frame and accuracy information hints in their names:
+
+- `global` means an absolute world frame, such as that provided by GNSS.
+- `local` means a frame that relative to an initialization point, such as the position of an IMU on boot.
+- `relaxed` means that the mode does not require or rely on accurate data: as long as sensors are providing some data the state is considered valid.
+ Relaxed conditions are used for modes where some sensor data is considered more important than none at all, such as when calculating position via optical flow velocity measurements.
+ By contrast, a position mode that is not relaxed requires reliable sensor data, and will block arming if inaccuracy is detected.
+
+Note that a global position requirement can be met if you have a valid _local position_, by mapping the local frame to a global position.
+This can be done by setting the global position of the local origin using the MAVLink message [SET_GPS_GLOBAL_ORIGIN](https://mavlink.io/en/messages/common.html#SET_GPS_GLOBAL_ORIGIN), either directly or via a GCS (see [External Position Estimate > Enabling Auto Modes with a Local Position](../ros/external_position_estimation#enabling-auto-modes-with-a-local-position)).
+Similarly, if the vehicle has `mode_req_local_position_relaxed`, then you can map to a global position in order to meet the `global_position_relaxed` requirement.
+This allows PX4 automatic flight modes that require a global position to be used locally, such as Mission and Return.
+
+
+## Fixed-wing (VEHICLE_TYPE_FIXED_WING)
+
+### [Manual Mode](../flight_modes_fw/manual.md) (NAVIGATION_STATE_MANUAL)
+
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Stabilized Mode](../flight_modes_fw/stabilized.md) (NAVIGATION_STATE_STAB)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Acro Mode](../flight_modes_fw/acro.md) (NAVIGATION_STATE_ACRO)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Altitude Mode](../flight_modes_fw/altitude.md) (NAVIGATION_STATE_ALTCTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Cruise Mode](../flight_modes_fw/cruise.md) (NAVIGATION_STATE_POSCTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Guided Course Mode](../flight_modes_fw/guided_course.md) (NAVIGATION_STATE_GUIDED_COURSE)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Hold Mode](../flight_modes_fw/hold.md) (NAVIGATION_STATE_AUTO_LOITER)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position_relaxed`](#mode_req_global_position_relaxed)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Takeoff Mode](../flight_modes_fw/takeoff.md) (NAVIGATION_STATE_AUTO_TAKEOFF)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+
+### [Descend Mode](../flight_modes_fw/descend.md) (NAVIGATION_STATE_DESCEND)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Land Mode](../flight_modes_fw/land.md) (NAVIGATION_STATE_AUTO_LAND)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Return Mode](../flight_modes_fw/return.md) (NAVIGATION_STATE_AUTO_RTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position_relaxed`](#mode_req_global_position_relaxed)
+- [`mode_req_home_position`](#mode_req_home_position)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Mission Mode](../flight_modes_fw/mission.md) (NAVIGATION_STATE_AUTO_MISSION)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position_relaxed`](#mode_req_global_position_relaxed)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_mission`](#mode_req_mission)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Offboard Mode](../flight_modes_fw/offboard.md) (NAVIGATION_STATE_OFFBOARD)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_offboard_signal`](#mode_req_offboard_signal)
+
+### Modes Without a Dedicated Page
+
+The following internal navigation states have no distinct user-facing behaviour or documentation page on this frame type:
+
+- **NAVIGATION_STATE_ALTITUDE_CRUISE** — Behaves identically to [Altitude Mode](../flight_modes_fw/altitude.md) on fixed-wing frames — the control-mode flags are the same and there is no separate implementation.
+- **NAVIGATION_STATE_POSITION_SLOW** — Behaves identically to [Position Mode](../flight_modes_fw/position.md) on fixed-wing frames — the control-mode flags are the same and there is no separate implementation.
+- **NAVIGATION_STATE_ORBIT** — Not implemented for fixed-wing frames; [Hold Mode](../flight_modes_fw/hold.md) is used instead for orbiting a point.
+- **NAVIGATION_STATE_AUTO_VTOL_TAKEOFF** — VTOL-specific transition state used for vertical takeoff before transitioning to forward flight; not applicable to plain fixed-wing frames.
+- **NAVIGATION_STATE_AUTO_PRECLAND** — Not implemented for fixed-wing frames (precision landing requires hover capability).
+- **NAVIGATION_STATE_AUTO_FOLLOW_TARGET** — Not implemented for fixed-wing frames (Follow Me is a multicopter-only flight task).
+- **NAVIGATION_STATE_TERMINATION** — Internal flight-termination failsafe state. Not user-selectable; entered automatically when a failsafe action escalates to termination.
+
+## Multicopter (VEHICLE_TYPE_ROTARY_WING)
+
+### [Manual/Stabilized Mode](../flight_modes_mc/manual_stabilized.md) (NAVIGATION_STATE_MANUAL)
+
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Manual/Stabilized Mode](../flight_modes_mc/manual_stabilized.md) (NAVIGATION_STATE_STAB)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Acro Mode](../flight_modes_mc/acro.md) (NAVIGATION_STATE_ACRO)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Altitude Mode](../flight_modes_mc/altitude.md) (NAVIGATION_STATE_ALTCTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Altitude Cruise Mode](../flight_modes_mc/altitude_cruise.md) (NAVIGATION_STATE_ALTITUDE_CRUISE)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Position Mode](../flight_modes_mc/position.md) (NAVIGATION_STATE_POSCTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Position Slow Mode](../flight_modes_mc/position_slow.md) (NAVIGATION_STATE_POSITION_SLOW)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_manual_control`](#mode_req_manual_control)
+
+### [Hold Mode](../flight_modes_mc/hold.md) (NAVIGATION_STATE_AUTO_LOITER)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position`](#mode_req_global_position)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Orbit Mode](../flight_modes_mc/orbit.md) (NAVIGATION_STATE_ORBIT)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Takeoff Mode](../flight_modes_mc/takeoff.md) (NAVIGATION_STATE_AUTO_TAKEOFF)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+
+### [Descend Mode](../flight_modes_mc/descend.md) (NAVIGATION_STATE_DESCEND)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Land Mode](../flight_modes_mc/land.md) (NAVIGATION_STATE_AUTO_LAND)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position_relaxed`](#mode_req_local_position_relaxed)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Precision Landing](../advanced_features/precland.md) (NAVIGATION_STATE_AUTO_PRECLAND)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Return Mode](../flight_modes_mc/return.md) (NAVIGATION_STATE_AUTO_RTL)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position`](#mode_req_global_position)
+- [`mode_req_home_position`](#mode_req_home_position)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+
+### [Mission Mode](../flight_modes_mc/mission.md) (NAVIGATION_STATE_AUTO_MISSION)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_global_position`](#mode_req_global_position)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_mission`](#mode_req_mission)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Follow Me Mode](../flight_modes_mc/follow_me.md) (NAVIGATION_STATE_AUTO_FOLLOW_TARGET)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_local_alt`](#mode_req_local_alt)
+- [`mode_req_local_position`](#mode_req_local_position)
+- [`mode_req_prevent_arming`](#mode_req_prevent_arming)
+- [`mode_req_wind_and_flight_time_compliance`](#mode_req_wind_and_flight_time_compliance)
+
+### [Offboard Mode](../flight_modes_mc/offboard.md) (NAVIGATION_STATE_OFFBOARD)
+
+- [`mode_req_angular_velocity`](#mode_req_angular_velocity)
+- [`mode_req_attitude`](#mode_req_attitude)
+- [`mode_req_offboard_signal`](#mode_req_offboard_signal)
+
+### Modes Without a Dedicated Page
+
+The following internal navigation states have no distinct user-facing behaviour or documentation page on this frame type:
+
+- **NAVIGATION_STATE_GUIDED_COURSE** — Not implemented for multicopters (course-hold is a fixed-wing-only flight task).
+- **NAVIGATION_STATE_AUTO_VTOL_TAKEOFF** — VTOL-specific transition state used for vertical takeoff before transitioning to forward flight; not applicable to plain multicopter frames.
+- **NAVIGATION_STATE_TERMINATION** — Internal flight-termination failsafe state. Not user-selectable; entered automatically when a failsafe action escalates to termination.
diff --git a/docs/en/flight_modes/offboard.md b/docs/en/flight_modes/offboard.md
index a981b592c65..9e9f4b50d18 100644
--- a/docs/en/flight_modes/offboard.md
+++ b/docs/en/flight_modes/offboard.md
@@ -17,7 +17,9 @@ A good understanding of [PX4 controller diagrams](../flight_stack/controller_dia
The vehicle obeys position, velocity, acceleration, attitude, attitude rates or thrust/torque setpoints provided by some source that is external to the flight stack, such as a companion computer.
The setpoints may be provided using MAVLink (or a MAVLink API such as [MAVSDK](https://mavsdk.mavlink.io/)) or by [ROS 2](../ros2/index.md).
-PX4 requires that the external controller provides a continuous "proof of life" signal by streaming any of the supported MAVLink setpoint messages or the ROS 2 [OffboardControlMode](../msg_docs/OffboardControlMode.md) message.
+## Technical Summary
+
+PX4 requires that the external controller provides a continuous 2Hz "proof of life" signal, by streaming any of the supported MAVLink setpoint messages or the ROS 2 [OffboardControlMode](../msg_docs/OffboardControlMode.md) message.
The stream should be active before switching to Offboard mode, and PX4 will trigger the configured Offboard-loss failsafe action ([COM_OBL_RC_ACT](../advanced_config/parameter_reference.md#COM_OBL_RC_ACT)) if proof-of-life messages are not received within the timeout configured by [COM_OF_LOSS_T](#COM_OF_LOSS_T).
::: info
@@ -31,6 +33,29 @@ The stream should be active before switching to Offboard mode, and PX4 will trig
:::
+
+
+### Mode Requirements — Fixed-Wing
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_offboard_signal`](../flight_modes/mode_requirements.md#mode_req_offboard_signal) — Offboard heartbeat
+
+
+
+
+### Mode Requirements — Multicopter
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_offboard_signal`](../flight_modes/mode_requirements.md#mode_req_offboard_signal) — Offboard heartbeat
+
+
+
## Description
Offboard mode is used for controlling vehicle movement and attitude, by setting position, velocity, acceleration, attitude, attitude rates or thrust/torque setpoints.
diff --git a/docs/en/flight_modes_fw/acro.md b/docs/en/flight_modes_fw/acro.md
index 7f0ec774556..b326dec2dd9 100644
--- a/docs/en/flight_modes_fw/acro.md
+++ b/docs/en/flight_modes_fw/acro.md
@@ -16,6 +16,17 @@ Manual mode for performing acrobatic maneuvers e.g. rolls, flips, stalls and acr
Roll-pitch-yaw stick inputs are translated to angular rate commands that are stabilized by autopilot.
Throttle is passed directly to control allocation.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
| Parameter | Description |
diff --git a/docs/en/flight_modes_fw/altitude.md b/docs/en/flight_modes_fw/altitude.md
index badb1a92b67..5596bf8a522 100644
--- a/docs/en/flight_modes_fw/altitude.md
+++ b/docs/en/flight_modes_fw/altitude.md
@@ -43,6 +43,19 @@ The vehicle course is not maintained, and can drift due to wind.
- Manual control input is required (such as RC control, joystick).
- An altitude measurement source is required (usually barometer or GPS)
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
The mode is affected by the following parameters:
diff --git a/docs/en/flight_modes_fw/cruise.md b/docs/en/flight_modes_fw/cruise.md
index 6c0044e66d0..67db4f4a274 100644
--- a/docs/en/flight_modes_fw/cruise.md
+++ b/docs/en/flight_modes_fw/cruise.md
@@ -37,6 +37,20 @@ Airspeed is also stabilized if an airspeed sensor is present.
- Manual control input is required (such as RC control, joystick).
- An altitude measurement source is required (usually barometer or GPS)
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
The mode is affected by the following parameters:
diff --git a/docs/en/flight_modes_fw/descend.md b/docs/en/flight_modes_fw/descend.md
index 524b034ff0e..627d07b019d 100644
--- a/docs/en/flight_modes_fw/descend.md
+++ b/docs/en/flight_modes_fw/descend.md
@@ -14,7 +14,9 @@ It is the last resort used when the vehicle must come down but has no valid posi
:::
-## When It Occurs
+## Technical Description
+
+### When It Occurs
Descend mode is at the bottom of the failsafe chain (along with [Flight termination](../advanced_config/flight_termination.md)):
@@ -33,13 +35,25 @@ For example:
- Losing the position estimate in [Hold](../flight_modes_fw/hold.md), [Mission](../flight_modes_fw/mission.md) or [Return](../flight_modes_fw/return.md): with no position to hold, fly to, or return with, the failsafe escalates down to _Descend_.
- A Return or Land failsafe (from manual control loss, GCS/data link loss, low battery, geofence breach, …) triggered while no valid position estimate is available: Return and Land can't run, so it degrades to _Descend_.
-## Exiting Descend
+### Exiting Descend
Descend ends when either the:
- failsafe condition is resolved (e.g. the position estimate recovers), and the vehicle returns to its previous mode; or
- pilot takes over by switching to a manual mode ([Cruise](../flight_modes_fw/cruise.md), [Altitude](../flight_modes_fw/altitude.md) or [Stabilized](../flight_modes_fw/stabilized.md)).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
## See Also
- [Descend Mode (MC)](../flight_modes_mc/descend.md)
diff --git a/docs/en/flight_modes_fw/guided_course.md b/docs/en/flight_modes_fw/guided_course.md
index 10535622536..45c42d4aa6a 100644
--- a/docs/en/flight_modes_fw/guided_course.md
+++ b/docs/en/flight_modes_fw/guided_course.md
@@ -47,7 +47,21 @@ The navigator mode (`course.cpp`) sets a position setpoint with `course` (ground
The fixed-wing mode manager (`FixedWingModeManager`) detects the finite `course` field and bypasses normal waypoint sequencing, calling `navigateBearing()` from the directional guidance library to compute lateral acceleration and course setpoints.
Longitudinal control targets the altitude and airspeed from the setpoint.
-## Failsafe Behaviour
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
+### Failsafe Behaviour
Guided Course is classified as an `AUTO` mode for failsafe purposes.
The following failsafe exception parameters apply:
@@ -63,6 +77,9 @@ If the GCS link drops, the vehicle will continue on its last commanded course in
It is strongly recommended to either leave the datalink failsafe active or ensure a secondary safety mechanism (e.g. geofence, battery failsafe) is in place.
:::
+
+
+
## Parameters
| Parameter | Description |
diff --git a/docs/en/flight_modes_fw/hold.md b/docs/en/flight_modes_fw/hold.md
index bb4c07e327d..7fe80726ea5 100644
--- a/docs/en/flight_modes_fw/hold.md
+++ b/docs/en/flight_modes_fw/hold.md
@@ -94,6 +94,21 @@ Any remaining altitude error is then removed by climbing or sinking once the veh
The ramp is (re)started whenever the target altitude changes; a reposition that keeps the same altitude does not change the vehicle's altitude.
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_global_position_relaxed) — Position measurement updates in a global coordinate frame but accepts poor accuracy
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
## Parameters
Hold mode behaviour can be configured using the parameters below.
diff --git a/docs/en/flight_modes_fw/land.md b/docs/en/flight_modes_fw/land.md
index 5fe58fe1bde..8fd54ca4293 100644
--- a/docs/en/flight_modes_fw/land.md
+++ b/docs/en/flight_modes_fw/land.md
@@ -37,6 +37,20 @@ The vehicle will flare if configured to do so (see [Flaring](../flight_modes_fw/
[Manual nudging](../flight_modes_fw/mission.md#automatic-abort) and [automatic land abort](../flight_modes_fw/mission.md#nudging) are not available in land mode.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
### Parameters
Land mode behaviour can be configured using the parameters below.
diff --git a/docs/en/flight_modes_fw/manual.md b/docs/en/flight_modes_fw/manual.md
index d3c9c23291b..26e4bc9de8d 100644
--- a/docs/en/flight_modes_fw/manual.md
+++ b/docs/en/flight_modes_fw/manual.md
@@ -20,6 +20,16 @@ Manual mode where stick input is sent directly to control allocation (for "fully
This is the only mode that overrides the FMU (commands are sent via the safety coprocessor). It provides a safety mechanism that allows full control of throttle, elevator, ailerons and rudder via RC in the event of an FMU firmware malfunction.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
| Parameter | Description |
diff --git a/docs/en/flight_modes_fw/mission.md b/docs/en/flight_modes_fw/mission.md
index 1f3e4cd0e62..4e4e9b0702a 100644
--- a/docs/en/flight_modes_fw/mission.md
+++ b/docs/en/flight_modes_fw/mission.md
@@ -8,12 +8,28 @@ The mission is typically created and uploaded with a Ground Control Station (GCS
::: info
- This mode requires a global 3d position estimate (from GPS or inferred from a [local position](../ros/external_position_estimation.md#enabling-auto-modes-with-a-local-position)).
-- The vehicle must be armed before this mode can be engaged.
+- The mission will start once the vehicle is armed.
- This mode is automatic - no user intervention is _required_ to control the vehicle.
-- RC control switches can be used to change flight modes on any vehicle.
+- Sticks/switches can be used to switch out of mission mode on any vehicle.
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_global_position_relaxed) — Position measurement updates in a global coordinate frame but accepts poor accuracy
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_mission`](../flight_modes/mode_requirements.md#mode_req_mission) — Valid mission in autopilot's storage
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
## Description
Missions are usually created in a ground control station (e.g. [QGroundControl](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/plan_view/plan_view.html)) and uploaded prior to launch.
diff --git a/docs/en/flight_modes_fw/return.md b/docs/en/flight_modes_fw/return.md
index 11536be472a..6aac72b368b 100644
--- a/docs/en/flight_modes_fw/return.md
+++ b/docs/en/flight_modes_fw/return.md
@@ -47,6 +47,22 @@ When the destination is a rally point or the home location, on arrival the vehic
The vehicle can be forced to land at the destination by changing [RTL_LAND_DELAY](#RTL_LAND_DELAY) so it is not -1.
In this case the vehicle will land in the same way as [Land mode](../flight_modes_fw/land.md).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_global_position_relaxed) — Position measurement updates in a global coordinate frame but accepts poor accuracy
+- [`mode_req_home_position`](../flight_modes/mode_requirements.md#mode_req_home_position) — Global home reference must be set
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
## Parameters
The RTL parameters are listed in [Parameter Reference > Return Mode](../advanced_config/parameter_reference.md#return-mode).
diff --git a/docs/en/flight_modes_fw/stabilized.md b/docs/en/flight_modes_fw/stabilized.md
index 4e0960ef3ef..cf3e8ab5414 100644
--- a/docs/en/flight_modes_fw/stabilized.md
+++ b/docs/en/flight_modes_fw/stabilized.md
@@ -37,6 +37,18 @@ The vehicle course and altitude are not maintained, and can drift due to wind.
Can be used to manually change the side slip of the vehicle.
- Manual control input is required (such as RC control, joystick).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
The mode is affected by the following parameters:
diff --git a/docs/en/flight_modes_fw/takeoff.md b/docs/en/flight_modes_fw/takeoff.md
index 54769f7974d..fbadf5cbae9 100644
--- a/docs/en/flight_modes_fw/takeoff.md
+++ b/docs/en/flight_modes_fw/takeoff.md
@@ -55,6 +55,18 @@ If the local position is invalid or becomes invalid while executing the takeoff,
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+
+
+
### Parameters
Parameters that affect both catapult/hand-launch and runway takeoffs:
diff --git a/docs/en/flight_modes_mc/acro.md b/docs/en/flight_modes_mc/acro.md
index 5f2910a0636..cc908d71299 100644
--- a/docs/en/flight_modes_mc/acro.md
+++ b/docs/en/flight_modes_mc/acro.md
@@ -25,6 +25,17 @@ Manual control input is required (such as RC control, joystick):
- Throttle: Manual control via RC sticks.
RC input is sent directly to control allocation.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Stick Input Mapping
The default values for expo and rate [parameters](#parameters) are _beginner friendly_, reducing the chance that users will flip the vehicle when first trying this mode, or when using Acro mode for manual rate tuning.
diff --git a/docs/en/flight_modes_mc/altitude.md b/docs/en/flight_modes_mc/altitude.md
index b08688caa0d..d87ec9f5d27 100644
--- a/docs/en/flight_modes_mc/altitude.md
+++ b/docs/en/flight_modes_mc/altitude.md
@@ -39,6 +39,19 @@ The horizontal position of the vehicle can move due to wind (or pre-existing mom
- Yaw: Assistance from autopilot to stabilize the attitude rate.
Position of RC stick maps to the rate of rotation of vehicle in that orientation.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
The mode is affected by the following parameters:
diff --git a/docs/en/flight_modes_mc/altitude_cruise.md b/docs/en/flight_modes_mc/altitude_cruise.md
index 87728cc3119..27adbdfc62b 100644
--- a/docs/en/flight_modes_mc/altitude_cruise.md
+++ b/docs/en/flight_modes_mc/altitude_cruise.md
@@ -34,6 +34,19 @@ A manual mode that is similar to [Altitude mode](../flight_modes_mc/altitude.md)
- Manual control input is required (such as RC control, joystick) to enter this mode. Other than in all other manual modes, it's though possible to disable the manual control loss failsafe by setting the corresponding flag in [COM_RCL_EXCEPT](../advanced_config/parameter_reference.md#COM_RCL_EXCEPT). In that case the current altitude, tilt and heading are kept until the manual control link is regained or the mode is exited.
It is highly recommended to only disable the manual control loss failsafe for this mode if there is a stable data link connection to the vehicle at all times, and to enable the data link loss failsafe through [NAV_DLL_ACT](../advanced_config/parameter_reference.md#NAV_DLL_ACT).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Parameters
Most of the relevant parameters are already covered in the corresponding section in the [Altitude mode](../flight_modes_mc/altitude.md). Here a list of parameters of particular importance for Altitude Cruise.
diff --git a/docs/en/flight_modes_mc/descend.md b/docs/en/flight_modes_mc/descend.md
index b3348d1cefc..f137b768928 100644
--- a/docs/en/flight_modes_mc/descend.md
+++ b/docs/en/flight_modes_mc/descend.md
@@ -14,7 +14,9 @@ It is the last resort used when the vehicle must come down but has no valid posi
:::
-## When It Occurs
+## Technical Description
+
+### When It Occurs
Descend mode is at the bottom of the failsafe chain (along with [Flight termination](../advanced_config/flight_termination.md)):
@@ -33,7 +35,7 @@ For example:
- Losing the position estimate in [Hold](../flight_modes_mc/hold.md), [Mission](../flight_modes_mc/mission.md) or [Return](../flight_modes_mc/return.md): with no position to hold, fly to, or return with, the failsafe escalates down to _Descend_.
- A Return or Land failsafe (from manual control loss, GCS/data link loss, low battery, geofence breach, …) triggered while no valid position estimate is available: Return and Land can't run, so it degrades to _Descend_.
-## Exiting Descend
+### Exiting Descend
Descend ends when either the:
@@ -41,6 +43,18 @@ Descend ends when either the:
- pilot takes over by switching to a manual mode ([Position](../flight_modes_mc/position.md), [Altitude](../flight_modes_mc/altitude.md) or [Stabilized](../flight_modes_mc/manual_stabilized.md)).
On a multicopter, moving the sticks does this [by default](../flight_modes_mc/land.md#MAN_OVERRIDE_SPD).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
## See Also
- [Descend Mode (FW)](../flight_modes_fw/descend.md)
diff --git a/docs/en/flight_modes_mc/follow_me.md b/docs/en/flight_modes_mc/follow_me.md
index 57931589e04..6fbe413fff9 100644
--- a/docs/en/flight_modes_mc/follow_me.md
+++ b/docs/en/flight_modes_mc/follow_me.md
@@ -20,6 +20,21 @@ _Follow Me_ mode allows a multicopter to autonomously hold position and altitude
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
## Overview

diff --git a/docs/en/flight_modes_mc/hold.md b/docs/en/flight_modes_mc/hold.md
index c81e4b3addb..e65de49b332 100644
--- a/docs/en/flight_modes_mc/hold.md
+++ b/docs/en/flight_modes_mc/hold.md
@@ -31,6 +31,21 @@ The vehicle will first ascend to [NAV_MIN_LTR_ALT](#NAV_MIN_LTR_ALT) if the mode
Stick movement will change the vehicle to [Position mode](../flight_modes_mc/position.md) (by [default](#MAN_OVERRIDE_SPD)).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position`](../flight_modes/mode_requirements.md#mode_req_global_position) — Position measurement updates in a global coordinate frame
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
### Parameters
Hold mode behaviour can be configured using the parameters below.
diff --git a/docs/en/flight_modes_mc/land.md b/docs/en/flight_modes_mc/land.md
index d7d2f539ace..a67aae484a1 100644
--- a/docs/en/flight_modes_mc/land.md
+++ b/docs/en/flight_modes_mc/land.md
@@ -27,6 +27,20 @@ The vehicle descends at the rate specified in [MPC_LAND_SPEED](#MPC_LAND_SPEED)
Stick movement will change the vehicle to [Position mode](../flight_modes_mc/position.md) (by [default](#MAN_OVERRIDE_SPD)).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
### Parameters
Land mode behaviour can be configured using the parameters below.
diff --git a/docs/en/flight_modes_mc/manual_stabilized.md b/docs/en/flight_modes_mc/manual_stabilized.md
index 36a9ae345f8..df138e54a53 100644
--- a/docs/en/flight_modes_mc/manual_stabilized.md
+++ b/docs/en/flight_modes_mc/manual_stabilized.md
@@ -1,4 +1,4 @@
-# Stabilized Mode (Multicopter)
+# Manual/Stabilized Mode (Multicopter)
@@ -6,14 +6,15 @@ The _Stabilized_ manual mode stabilizes and levels the multicopter when the RC c
To move/fly the vehicle you move the sticks outside of the centre.
::: info
-This mode is also enabled if you set the flight mode to _Manual_.
+PX4 has separate MC _Manual_ and _Stabilized_ navigation states, which correspond to separate flight modes that can be set (and displayed via MAVLink).
+These modes behave identically in flight, but have different arming requirements — see [Mode Requirements](#mode-requirements) below.
:::
When sticks are outside the centre, the roll and pitch sticks control the _angle_ of the vehicle (attitude) around the respective axes, the yaw stick controls the rate of rotation above the horizontal plane, and the throttle controls altitude/speed.
As soon as you release the control sticks they will return to the center deadzone.
The multicopter will level out and stop once the roll and pitch sticks are centered.
-The vehicle will then hover in place/maintain altitude - provided it is properly balanced, throttle is set appropriately (see [below](#params)), and no external forces are applied (e.g. wind).
+The vehicle will then hover in place/maintain altitude - provided it is properly balanced, throttle is set appropriately (see [below](#parameters)), and no external forces are applied (e.g. wind).
The craft will drift in the direction of any wind and you have to control the throttle to hold altitude.

@@ -27,7 +28,7 @@ RC mode where centered sticks level vehicle.
:::
The pilot's inputs are passed as roll and pitch angle commands and a yaw rate command.
-Throttle is rescaled (see [below](#params)) and passed directly to control allocation.
+Throttle is rescaled (see [below](#parameters)) and passed directly to control allocation.
The autopilot controls the attitude, meaning it regulates the roll and pitch angles to zero when the RC sticks are centered inside the controller deadzone (consequently leveling-out the attitude).
The autopilot does not compensate for drift due to wind (or other sources).
@@ -44,7 +45,35 @@ The autopilot does not compensate for drift due to wind (or other sources).
- Yaw: Assistance from autopilot to stabilize the attitude rate.
Position of RC stick maps to the rate of rotation of vehicle in that orientation.
-
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
+::: info
+These requirements are form arming in Stabilized mode.
+:::
+
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
+::: info
+These requirements are for arming in Manual mode.
+:::
## Parameters
diff --git a/docs/en/flight_modes_mc/mission.md b/docs/en/flight_modes_mc/mission.md
index a28653ff569..7cbeff86aa5 100644
--- a/docs/en/flight_modes_mc/mission.md
+++ b/docs/en/flight_modes_mc/mission.md
@@ -17,6 +17,22 @@ The mission is typically created and uploaded with a Ground Control Station (GCS
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position`](../flight_modes/mode_requirements.md#mode_req_global_position) — Position measurement updates in a global coordinate frame
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_mission`](../flight_modes/mode_requirements.md#mode_req_mission) — Valid mission in autopilot's storage
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
## Description
Missions are usually created in a ground control station (e.g. [QGroundControl](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/plan_view/plan_view.html)) and uploaded prior to launch.
diff --git a/docs/en/flight_modes_mc/orbit.md b/docs/en/flight_modes_mc/orbit.md
index db4fe397203..80c25ae5694 100644
--- a/docs/en/flight_modes_mc/orbit.md
+++ b/docs/en/flight_modes_mc/orbit.md
@@ -8,18 +8,30 @@ The _Orbit_ guided flight mode allows you to command a multicopter (or VTOL in m
- Mode is automatic - no user intervention is _required_ to control the vehicle.
- Mode requires at least a valid local position estimate (does not require a global position).
- - Flying vehicles can't switch to this mode without valid local position.
- Flying vehicles will failsafe if they lose the position estimate.
- Mode prevents arming (vehicle cannot be armed while this mode is selected).
- Mode requires wind and flight time are within allowed limits (specified via parameters).
-- This mode is currently only supported on multicopter (or VTOL in MC mode).
+- Mode is currently only supported on multicopter (or VTOL in MC mode).
- RC stick movement can control ascent/descent and orbit speed and direction.
-- The mode can be triggered using the [MAV_CMD_DO_ORBIT](https://mavlink.io/en/messages/common.html#MMAV_CMD_DO_ORBIT) MAVLink command.
-
-
+- Mode can be triggered using the [MAV_CMD_DO_ORBIT](https://mavlink.io/en/messages/common.html#MMAV_CMD_DO_ORBIT) MAVLink command.
:::
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+- [`mode_req_wind_and_flight_time_compliance`](../flight_modes/mode_requirements.md#mode_req_wind_and_flight_time_compliance) — Safety compliance limits on wind and flight time.
+
+
+
## Overview

diff --git a/docs/en/flight_modes_mc/position.md b/docs/en/flight_modes_mc/position.md
index f577f3f2344..24ebdf92f66 100644
--- a/docs/en/flight_modes_mc/position.md
+++ b/docs/en/flight_modes_mc/position.md
@@ -56,6 +56,20 @@ Centered sticks level vehicle and hold it to fixed altitude and position against
- Yaw: Assistance from autopilot to stabilize the attitude rate.
Position of RC stick maps to the rate of rotation of vehicle in that orientation.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
### Parameters
All the parameters in the [Multicopter Position Control](../advanced_config/parameter_reference.md#multicopter-position-control) group are relevant. A few parameters of particular note are listed below.
diff --git a/docs/en/flight_modes_mc/position_slow.md b/docs/en/flight_modes_mc/position_slow.md
index f11db844d3f..f75af50de52 100644
--- a/docs/en/flight_modes_mc/position_slow.md
+++ b/docs/en/flight_modes_mc/position_slow.md
@@ -14,6 +14,20 @@ The velocity limits can be set using parameters, from an [RC Controller](../gett
Limits set using an RC controller override those set by MAVLink, which in turn override those set using parameters.
The limits can only be reduced below those for normal _Position_ mode.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position_relaxed`](../flight_modes/mode_requirements.md#mode_req_local_position_relaxed) — Position relative to EKF2 origin ('0') point but accepts poor accuracy
+- [`mode_req_manual_control`](../flight_modes/mode_requirements.md#mode_req_manual_control) — Requires stick input
+
+
+
## Set Limits using Parameters
The maximum values for slow mode horizontal velocity, vertical velocity, and yaw rate can be set using parameters.
diff --git a/docs/en/flight_modes_mc/return.md b/docs/en/flight_modes_mc/return.md
index af8fcc431b2..5ce1793cd03 100644
--- a/docs/en/flight_modes_mc/return.md
+++ b/docs/en/flight_modes_mc/return.md
@@ -39,6 +39,22 @@ For this return type the copter:
- It waits for a configurable time ([RTL_LAND_DELAY](#RTL_LAND_DELAY)), which may be used for deploying landing gear.
- Then lands.
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_global_position`](../flight_modes/mode_requirements.md#mode_req_global_position) — Position measurement updates in a global coordinate frame
+- [`mode_req_home_position`](../flight_modes/mode_requirements.md#mode_req_home_position) — Global home reference must be set
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+- [`mode_req_prevent_arming`](../flight_modes/mode_requirements.md#mode_req_prevent_arming) — Mode prevents arming
+
+
+
### Minimum Return Altitude
By default the _minimum return altitude_ is set using [RTL_RETURN_ALT](#RTL_RETURN_ALT), and the vehicle will just return at the higher of `RTL_RETURN_ALT` or the initial vehicle altitude.
diff --git a/docs/en/flight_modes_mc/takeoff.md b/docs/en/flight_modes_mc/takeoff.md
index 5119b06dfa6..e3f07ac9a34 100644
--- a/docs/en/flight_modes_mc/takeoff.md
+++ b/docs/en/flight_modes_mc/takeoff.md
@@ -15,8 +15,6 @@ The _Takeoff_ flight mode causes the vehicle to take off to a specified height a
- Stick movement will [by default](#MAN_OVERRIDE_SPD) change the vehicle to [Position mode](../flight_modes_mc/position.md) unless prevented by the active failsafe state.
- The [Failure Detector](../config/safety.md#failure-detector) will automatically stop the engines if there is a problem on takeoff.
-
-
:::
## Technical Summary
@@ -25,6 +23,19 @@ A multi rotor ascends vertically to the altitude defined in [MIS_TAKEOFF_ALT](..
Stick movement will change the vehicle to [Position mode](../flight_modes_mc/position.md) (by [default](#MAN_OVERRIDE_SPD)).
+
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_angular_velocity`](../flight_modes/mode_requirements.md#mode_req_angular_velocity) — Angular velocity
+- [`mode_req_attitude`](../flight_modes/mode_requirements.md#mode_req_attitude) — Attitude/pose
+- [`mode_req_local_alt`](../flight_modes/mode_requirements.md#mode_req_local_alt) — Local altitude relative to EKF2 origin ('0') position
+- [`mode_req_local_position`](../flight_modes/mode_requirements.md#mode_req_local_position) — Position relative to EKF2 origin ('0') point
+
+
+
### Parameters
Takeoff is affected by the following parameters:
diff --git a/docs/package.json b/docs/package.json
index 48ac44e247d..7e846f74e95 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -14,7 +14,8 @@
"docs:get_alt_sidebar_windows": "python ./scripts/gen_alt_sidebar.py",
"start": "yarn docs:dev",
"linkcheck": "markdown_link_checker_sc -r .. -d docs -e en -i assets -u docs.px4.io",
- "build_docs_metadata_ubuntu": "(cd .. && Tools/ci/metadata_sync.sh --generate && Tools/ci/metadata_sync.sh --sync) && echo 'NOTE: These metadata changes are for local testing only and do not need to be merged.'"
+ "build_docs_metadata_ubuntu": "(cd .. && Tools/ci/metadata_sync.sh --generate && Tools/ci/metadata_sync.sh --sync) && echo 'NOTE: These metadata changes are for local testing only and do not need to be merged.'",
+ "docs:get_mode_requirements": "python3 ./scripts/get_mode_requirements/get_mode_requirements.py"
},
"dependencies": {
"@red-asuka/vitepress-plugin-tabs": "0.0.4",
diff --git a/docs/scripts/get_mode_requirements/CLAUDE.md b/docs/scripts/get_mode_requirements/CLAUDE.md
new file mode 100644
index 00000000000..f44f3142a5f
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/CLAUDE.md
@@ -0,0 +1,219 @@
+# get_mode_requirements
+
+## Purpose
+
+`get_mode_requirements.py` parses the PX4 flight mode requirements defined in
+`src/modules/commander/ModeUtil/mode_requirements.cpp` and generates Markdown documentation.
+
+It produces two kinds of output under `docs/en/`:
+
+- `flight_modes/mode_requirements.md` — a consolidated overview table for all vehicle types.
+- Per-mode requirement text injected directly into individual flight-mode pages using include markers.
+
+## Running
+
+From the repo root:
+
+```sh
+python3 docs/scripts/get_mode_requirements/get_mode_requirements.py
+```
+
+Or from the `docs/` directory:
+
+```sh
+python3 scripts/get_mode_requirements/get_mode_requirements.py
+```
+
+## Inline Injection
+
+The script locates each parent flight-mode doc (e.g. `flight_modes_fw/manual.md`) by scanning
+`docs/en/flight_modes/`, `docs/en/flight_modes_fw/`, `docs/en/flight_modes_mc/`, and
+`docs/en/advanced_features/` for a VitePress `` directive (or an existing
+`AUTO-GENERATED` sentinel block) that references the corresponding snippet name
+(`build_snippet_to_parent_map()`). This directory scan is what actually determines where a
+mode's requirements get injected — it is **independent** of the `doc` field in
+`mode_nav_state_defns.json`, which only controls headings/links in `mode_requirements.md`. A
+mode can have a `doc` pointing at a page that isn't scanned (or has no injection marker on it
+yet) and still trigger a "not mapped to a doc" warning; fix this by adding an
+`` marker pair
+(content between them can be empty — the script fills it in) to the actual doc page, and if
+that page lives outside the four scanned directories, add its directory to `search_dirs` in
+`build_snippet_to_parent_map()`.
+
+A single doc page can contain multiple injection points for different nav-states (e.g.
+`flight_modes/offboard.md` has one per vehicle type; `flight_modes_mc/manual_stabilized.md`
+has one for `NAVIGATION_STATE_STAB` and one for `NAVIGATION_STATE_MANUAL`, since those are
+distinct nav-states with different requirements that happen to share one doc page). Each
+generated block always starts its own `### Mode Requirements` heading, so when adding a second
+block to the same page, add distinguishing surrounding text/labels manually to avoid duplicate
+headings, then hand-write the marker pair; the script replaces the content and heading between
+the markers.
+
+It replaces that directive with the rendered content, wrapped in sentinel HTML comments:
+
+```markdown
+
+### Mode Requirements
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+- [`mode_req_manual_control`](xxx) - Requires stick input
+
+
+```
+
+On subsequent runs the sentinels are detected and the content between them is updated in-place,
+so the script is safe to re-run whenever `mode_requirements.cpp` changes.
+
+**Do not edit the text between the sentinel comments manually** — it will be overwritten on the
+next run. Make changes in `requirement_defns.json` or in `mode_requirements.cpp`.
+
+## Path Calculation
+
+The script resolves the repository root at runtime using `Path(__file__).resolve()` and
+navigating **three** parent directories up:
+
+```
+docs/scripts/get_mode_requirements/get_mode_requirements.py
+ -> docs/scripts/get_mode_requirements/ (parent)
+ -> docs/scripts/ (parent.parent)
+ -> docs/ (parent.parent.parent)
+ -> PX4-Autopilot/ (parent.parent.parent.parent) ← repo root
+```
+
+If this script is ever moved, update the `repo_root` line accordingly.
+
+## Adding or Changing Requirement Definitions
+
+`requirement_defns.json` (in the same directory as the script) maps each `mode_req_*` flag
+name to a human-readable `text`, an example `sensor`, and an extended `detail` string. Add
+new entries here when new requirement flags are introduced in `mode_requirements.cpp`.
+
+If the script encounters a flag in `mode_requirements.cpp` that has no entry in
+`requirement_defns.json`, it prints a `WARNING:` line to stderr and continues with empty
+`text`, `sensor`, and `detail` strings. If the JSON file is missing or malformed the script
+exits immediately with a clear `ERROR:` message.
+
+## Nav-State / Vehicle-Type Metadata
+
+`mode_nav_state_defns.json` (in the same directory as the script) maps every
+`(VEHICLE_TYPE_*, NAVIGATION_STATE_*)` pair to display metadata used when generating
+`mode_requirements.md` section headings and controlling per-mode warning behaviour.
+
+Each entry may contain:
+
+| Key | Type | Default | Meaning |
+|-----|------|---------|---------|
+| `label` | string | — | Human-readable mode name (e.g. `"Mission Mode"`) |
+| `doc` | string | — | Relative doc path used as a heading hyperlink (e.g. `"../flight_modes_fw/mission.md"`) |
+| `warn` | bool | `true` | Set to `false` to suppress warnings for this mode |
+| `status` | string | — | Short implementation-status note, used only when `doc` is absent (see below) |
+
+**Heading generation** (in `mode_requirements.md`):
+A mode is only given a full heading + requirement list in the main per-vehicle-type list if it
+has a `doc`. Modes with no `doc` are **not** given a bare heading in the main list — instead
+they are collected into a trailing `### Modes Without a Dedicated Page` summary for that
+vehicle type, rendered as `- **NAV_STATE** — {status}`. Use `status` to explain *why* there's
+no dedicated page — e.g. the mode isn't implemented for this vehicle type, or it behaves
+identically to another mode already documented, or it's an internal/failsafe state that isn't
+user-selectable. If a no-doc mode has no `status` text, the script prints a `WARNING:` to
+stderr and falls back to "Not currently documented."
+
+**Skipping snippet injection:** if an entry has `"warn": false` and no `"doc"` key, the script
+skips snippet processing for that mode without printing a warning. Use this for modes that
+are not real operational modes (e.g. `TERMINATION`) or that deliberately have no doc page.
+(This is orthogonal to `status`: `warn`/`doc` control snippet-injection behaviour, while
+`status` controls what's shown in the `mode_requirements.md` summary.)
+
+If the script encounters a `(vehicle_type, nav_state)` pair that has no entry at all in
+`mode_nav_state_defns.json`, it prints a `WARNING:` line to stderr. Add a new entry whenever
+a new navigation state is introduced in `mode_requirements.cpp`.
+
+**Ordering:** the key order of `NAVIGATION_STATE_*` entries within each vehicle-type block in
+`mode_nav_state_defns.json` — not the order `setRequirement()` calls appear in
+`mode_requirements.cpp` — determines the order modes are rendered in `mode_requirements.md`,
+for both the headed list and the trailing "Modes Without a Dedicated Page" summary. The
+intended order is low-to-high autonomy (e.g. `MANUAL` → `STAB` → `ACRO` → ... → `OFFBOARD`).
+`VEHICLE_TYPE_FIXED_WING` and `VEHICLE_TYPE_ROTARY_WING` are separate JSON objects, so keep
+their key order in sync manually when adding or reordering a mode. Any nav_state the parser
+finds that has no entry at all in `mode_nav_state_defns.json` is appended after all
+JSON-ordered modes (in addition to the existing "no entry" warning).
+
+The same principle applies one level up: the **top-level** key order of
+`mode_nav_state_defns.json` (currently `VEHICLE_TYPE_FIXED_WING` before
+`VEHICLE_TYPE_ROTARY_WING`) determines the order the `## Fixed-wing` / `## Multicopter`
+sections are rendered in `mode_requirements.md`. This is deliberate — vehicle types are
+discovered from `mode_requirements.cpp` into a Python `set`, whose iteration order is
+randomized per-process, so the render loops must never iterate that set/dict directly or the
+section order will vary between runs for no code reason. Any vehicle type found in the C++ but
+absent from the JSON is appended afterwards in sorted order.
+
+## Parser design
+
+`parse_requirements()` uses a single-pass token scanner. A single compiled regex (`_TOKEN`)
+identifies all meaningful tokens in the `getModeRequirements()` body in left-to-right order:
+single-line comments (skipped), `if (vehicle_type == X) {` (push condition), `} else {` (flip
+condition), `}` (pop), `{` (push None for non-conditional blocks), and `setRequirement(…)`
+calls. A condition stack tracks the innermost active vehicle-type condition; requirements are
+assigned to the appropriate vehicle-type set based on whether we are inside an if-branch, an
+else-branch, or unconditional code.
+
+This replaces an earlier two-pass regex approach that was fragile: its non-greedy `[\s\S]*?`
+pattern would span across unrelated blocks when a standalone `if` appeared before an `if-else`,
+producing silently wrong output for several modes.
+
+## Tests
+
+The test suite lives in `tests/` and is a pytest regression suite that guards against
+unintended changes to the parser output. It compares the live output of the parser against
+golden JSON snapshots:
+
+```
+tests/
+ __init__.py
+ test_get_mode_requirements.py ← the test module
+ test_data/
+ requirement_defns_golden.json ← expected requirement_defns contents
+ vehicle_modes_golden.json ← expected parse_requirements() output
+```
+
+- `test_data/requirement_defns_golden.json` — expected contents of the `requirement_defns`
+ dict (flag name → text / sensor / detail).
+- `test_data/vehicle_modes_golden.json` — expected output of `parse_requirements()` applied
+ to the checked-in `mode_requirements.cpp`: a per-vehicle-type, per-mode mapping of sorted
+ requirement flag names.
+
+`conftest.py` at the package root adds this directory to `sys.path` so pytest can import
+`get_mode_requirements` without any install step.
+
+The suite contains three tests:
+
+| Test | What it checks |
+|---|---|
+| `test_requirement_defns_matches_golden` | `requirement_defns` exactly matches the golden snapshot |
+| `test_vehicle_modes_matches_golden` | `parse_requirements()` output matches the golden snapshot; prints a per-mode diff on failure |
+| `test_all_requirement_flags_are_defined` | Every flag found in `mode_requirements.cpp` has an entry in `requirement_defns` |
+
+### Running the tests
+
+From this directory (`get_mode_requirements/`):
+
+```sh
+pytest tests/ -v
+```
+
+### When to run and update
+
+**Run the tests** whenever you change:
+- the parser logic in `get_mode_requirements.py`, or
+- `src/modules/commander/ModeUtil/mode_requirements.cpp`.
+
+**Update the golden files** after any intentional change to either of the above so that the
+snapshots stay in sync with the new expected output:
+
+```sh
+python tests/test_get_mode_requirements.py --update-golden
+```
+
+Then re-run the tests to confirm they pass, and commit the updated golden files alongside the
+parser or C++ changes.
diff --git a/docs/scripts/get_mode_requirements/conftest.py b/docs/scripts/get_mode_requirements/conftest.py
new file mode 100644
index 00000000000..c6b26c36a8d
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/conftest.py
@@ -0,0 +1,11 @@
+"""
+pytest configuration for get_mode_requirements tests.
+
+Adds the get_mode_requirements/ directory to sys.path so that
+ from get_mode_requirements import ...
+works inside tests/ without any path manipulation in the test files.
+"""
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
diff --git a/docs/scripts/get_mode_requirements/get_mode_requirements.py b/docs/scripts/get_mode_requirements/get_mode_requirements.py
new file mode 100644
index 00000000000..2841a208d45
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/get_mode_requirements.py
@@ -0,0 +1,499 @@
+#!/usr/bin/env python3
+"""
+Parses mode requirements in
+https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/commander/ModeUtil/mode_requirements.cpp
+and uses the result to generate Markdown documentation.
+
+Mode requirements are injected directly into parent flight-mode docs, wrapped in sentinel
+comments so subsequent runs update the content in-place.
+"""
+import re
+import argparse
+import sys
+from pathlib import Path
+import json
+
+# ---------------------------------------------------------------------------
+# Regex patterns for inline-injection mode
+# ---------------------------------------------------------------------------
+
+# Matches a VitePress include directive pointing at a mode_requirements snippet.
+# Captures the snippet stem (e.g. mode_requirements_fixed_wing_manual).
+_INCLUDE_RE = re.compile(
+ r''
+)
+
+# Matches an existing sentinel block for a given stem (built dynamically per stem).
+def _sentinel_re(stem: str) -> re.Pattern:
+ return re.compile(
+ r'.*?'
+ r'',
+ re.DOTALL,
+ )
+
+# Load requirement definitions from the JSON file alongside this script.
+_DEFNS_PATH = Path(__file__).resolve().parent / "requirement_defns.json"
+try:
+ with _DEFNS_PATH.open() as _f:
+ requirement_defns: dict = json.load(_f)
+except FileNotFoundError:
+ sys.exit(f"ERROR: requirement definitions file not found: {_DEFNS_PATH}")
+except json.JSONDecodeError as e:
+ sys.exit(f"ERROR: requirement definitions file is not valid JSON ({_DEFNS_PATH}): {e}")
+
+# Load nav-state / vehicle-type metadata from the JSON file alongside this script.
+_NAV_STATE_DEFNS_PATH = Path(__file__).resolve().parent / "mode_nav_state_defns.json"
+try:
+ with _NAV_STATE_DEFNS_PATH.open() as _f:
+ mode_nav_state_defns: dict = json.load(_f)
+except FileNotFoundError:
+ sys.exit(f"ERROR: mode nav state definitions file not found: {_NAV_STATE_DEFNS_PATH}")
+except json.JSONDecodeError as e:
+ sys.exit(f"ERROR: mode nav state definitions file is not valid JSON ({_NAV_STATE_DEFNS_PATH}): {e}")
+
+
+def _get_nav_state_def(vehicle_type: str, nav_state: str) -> dict:
+ """Return the nav-state definition for *(vehicle_type, nav_state)*.
+
+ Emits a WARNING to stderr if the pair has no entry in mode_nav_state_defns.json.
+ Returns an empty dict for unknown entries so callers can use .get() safely.
+ """
+ vt_map = mode_nav_state_defns.get(vehicle_type)
+ if vt_map is None:
+ print(
+ f"WARNING: vehicle type '{vehicle_type}' has no entries in "
+ "mode_nav_state_defns.json.",
+ file=sys.stderr,
+ )
+ return {}
+ entry = vt_map.get(nav_state)
+ if entry is None:
+ print(
+ f"WARNING: no entry in mode_nav_state_defns.json for "
+ f"({vehicle_type}, {nav_state}).",
+ file=sys.stderr,
+ )
+ return {}
+ return entry
+
+
+def _get_req_def(flag: str) -> dict:
+ """Return the requirement definition for *flag*, warning if it is unknown."""
+ if flag not in requirement_defns:
+ print(
+ f"WARNING: requirement '{flag}' found in mode_requirements.cpp "
+ "but has no entry in requirement_defns.json — text/detail will be empty.",
+ file=sys.stderr,
+ )
+ return {"text": "", "sensor": "", "detail": ""}
+ return requirement_defns[flag]
+
+
+# 1. Resolve the absolute path of the current script file.
+script_path = Path(__file__).resolve()
+# 2. Go up three directories from the script's location (docs/scripts/get_mode_requirements/ -> PX4-Autopilot/).
+# This finds the repository root.
+repo_root = script_path.parent.parent.parent.parent
+# 3. Construct the target file path relative to the repository root.
+SRC = repo_root / "src/modules/commander/ModeUtil/mode_requirements.cpp"
+
+docs_output_path_base = repo_root / "docs/en/"
+docs_output_path_flight_modes = docs_output_path_base / "flight_modes"
+
+
+# Tokeniser for getModeRequirements() function body.
+#
+# Walks the body left-to-right and emits one of six token kinds:
+# comment //… – ignored
+# if_vtype if (vehicle_type == X) { – push condition onto stack
+# else_open } else { – flip top of stack to else-side
+# close } – pop stack
+# open { – push None (non-conditional block)
+# req setRequirement(nav, flag) – record requirement
+#
+# \s* in the req pattern intentionally matches newlines so that multi-line
+# setRequirement() calls (e.g. NAVIGATION_STATE_ALTITUDE_CRUISE) are handled.
+_TOKEN = re.compile(
+ r'(?P//[^\n]*)'
+ r'|(?Pif\s*\(\s*vehicle_type\s*==\s*vehicle_status_s::'
+ r'(?PVEHICLE_TYPE_\w+)\s*\)\s*\{)'
+ r'|(?P\}\s*else\s*\{)'
+ r'|(?P\})'
+ r'|(?P\{)'
+ r'|(?PsetRequirement\(\s*'
+ r'(?Pvehicle_status_s::\w+)\s*,\s*'
+ r'flags\.(?P\w+)\s*\))'
+)
+
+
+def parse_requirements():
+ try:
+ text = SRC.read_text()
+ except FileNotFoundError:
+ print(f"Error: Source file not found at {SRC.resolve()}")
+ return {}
+
+ # Extract getModeRequirements() body
+ m = re.search(r"getModeRequirements\s*\([^)]*\)\s*\{(.*)\}", text, re.S)
+ if not m:
+ raise RuntimeError("Could not find getModeRequirements() body")
+ body = m.group(1)
+
+ # Discover all vehicle types mentioned in if-conditions
+ all_vtypes = set(re.findall(r'vehicle_status_s::(VEHICLE_TYPE_\w+)', body))
+ vehicle_modes = {vt: {} for vt in all_vtypes}
+
+ # Single-pass token scan with a condition stack.
+ #
+ # cond_stack entries:
+ # None – inside a non-vehicle-type block (or function body)
+ # (vtype, False) – inside if (vehicle_type == vtype) { … }
+ # (vtype, True) – inside } else { … } (i.e. NOT vtype)
+ cond_stack = []
+
+ for tok in _TOKEN.finditer(body):
+ kind = tok.lastgroup
+
+ if kind == 'comment':
+ pass # skip
+
+ elif kind == 'if_vtype':
+ cond_stack.append((tok.group('vtype'), False))
+
+ elif kind == 'else_open':
+ if cond_stack and cond_stack[-1] is not None:
+ # Flip the current vehicle-type condition to its else side
+ cond_stack[-1] = (cond_stack[-1][0], True)
+ else:
+ # else on a non-vehicle-type if – treat as a plain block
+ if cond_stack:
+ cond_stack.pop()
+ cond_stack.append(None)
+
+ elif kind == 'close':
+ if cond_stack:
+ cond_stack.pop()
+
+ elif kind == 'open':
+ cond_stack.append(None)
+
+ elif kind == 'req':
+ mode = tok.group('nav_state').replace('vehicle_status_s::', '')
+ req = tok.group('req_flag')
+
+ # The innermost vehicle-type condition determines which types apply
+ active = next((c for c in reversed(cond_stack) if c is not None), None)
+
+ if active is None:
+ vtypes = all_vtypes # default: all types
+ elif not active[1]:
+ vtypes = {active[0]} # if-branch: only this type
+ else:
+ vtypes = all_vtypes - {active[0]} # else-branch: all other types
+
+ for vt in vtypes:
+ vehicle_modes[vt].setdefault(mode, set()).add(req)
+
+ return vehicle_modes
+
+def build_snippet_to_parent_map(docs_base: Path) -> dict:
+ """Scan flight-mode doc directories and return {snippet_stem: parent_file_path}.
+
+ Recognises both the original VitePress include directive and existing
+ AUTO-GENERATED sentinel blocks so that re-runs work correctly.
+ """
+ mapping = {}
+ search_dirs = [
+ docs_base / "flight_modes",
+ docs_base / "flight_modes_fw",
+ docs_base / "flight_modes_mc",
+ docs_base / "advanced_features",
+ ]
+ for search_dir in search_dirs:
+ for md_file in sorted(search_dir.glob("*.md")):
+ text = md_file.read_text(encoding="utf-8")
+ # Check for @include directives
+ for m in _INCLUDE_RE.finditer(text):
+ stem = m.group(1)
+ mapping[stem] = md_file
+ # Check for existing sentinel blocks (covers re-runs after first inline)
+ for m in re.finditer(r'', text):
+ stem = m.group(1)
+ mapping[stem] = md_file
+ return mapping
+
+
+def inline_requirements_into_doc(parent_path: Path, snippet_stem: str, content: str) -> None:
+ """Inject *content* into *parent_path*, replacing an @include directive or
+ an existing AUTO-GENERATED sentinel block for *snippet_stem*.
+
+ The injected region is wrapped in sentinel HTML comments so subsequent runs
+ can find and replace it without any manual markers remaining in the file.
+ """
+ sentinel_start = f''
+ sentinel_end = f''
+ replacement = f'{sentinel_start}\n\n{content}\n{sentinel_end}'
+
+ original = parent_path.read_text(encoding="utf-8")
+
+ # Try existing sentinel block first (re-run case)
+ updated, n = _sentinel_re(snippet_stem).subn(replacement, original)
+ if n == 0:
+ # Try @include directive (first-time case)
+ include_pattern = re.compile(
+ r''
+ )
+ updated, n = include_pattern.subn(replacement, original)
+
+ if n == 0:
+ print(
+ f"⚠️ Warning: could not update '{parent_path}' for '{snippet_stem}'.\n"
+ f" The doc was found but contains neither an AUTO-GENERATED sentinel block\n"
+ f" nor an @include directive for this snippet.\n"
+ f" Add an injection point manually or remove the stale mapping."
+ )
+ return
+
+ if updated != original:
+ parent_path.write_text(updated, encoding="utf-8")
+ print(f"✅ Injected {snippet_stem} into {parent_path}")
+ # else: already up to date, no output needed
+
+
+if __name__ == "__main__":
+
+ _parser = argparse.ArgumentParser(
+ description="Generate PX4 flight-mode requirement documentation."
+ )
+ _parser.add_argument(
+ '--suppress_warnings',
+ action='store_true',
+ help='Suppress warnings about modes not listed in INVALID_MODES, NO_DOC_MODES, '
+ 'or the doc mapping. Resolve warnings by adding the mode to one of those '
+ 'lists or by providing a doc page with an injection point.',
+ )
+ args = _parser.parse_args()
+
+ vehicle_modes = parse_requirements()
+
+ # Convert sets to sorted lists for JSON output - i.e. all reqs are listed in the same order.
+ vehicle_modes_sorted = {
+ vtype: {mode: sorted(list(reqs)) for mode, reqs in modes.items()}
+ for vtype, modes in vehicle_modes.items()
+ }
+
+ print(json.dumps(vehicle_modes_sorted, indent=4))
+
+ # Vehicle-type render order: keyed off mode_nav_state_defns.json's top-level key order so
+ # it doesn't depend on set/dict iteration order (which is randomized per-process and was
+ # previously causing the Fixed-wing/Multicopter sections to swap between runs).
+ _vtype_order = list(mode_nav_state_defns.keys())
+ ordered_vtypes = [vt for vt in _vtype_order if vt in vehicle_modes_sorted] + sorted(
+ vt for vt in vehicle_modes_sorted if vt not in _vtype_order
+ )
+
+ # Warn about requirements defined in the JSON but absent from the parsed C++ file.
+ all_found_flags = {
+ req
+ for vtype in vehicle_modes_sorted.values()
+ for reqs in vtype.values()
+ for req in reqs
+ }
+ for flag in requirement_defns:
+ if flag not in all_found_flags:
+ print(
+ f"WARNING: '{flag}' is defined in requirement_defns.json "
+ "but was not found in mode_requirements.cpp.",
+ file=sys.stderr,
+ )
+
+ requirement_table = "| Requirement | Example |\n| --- | --- |\n"
+ for requirement, info in requirement_defns.items():
+ sensor_info = f" ({info['sensor']})" if info['sensor'] else ''
+ detail = f"
{info['detail']}" if info['detail'] else ''
+ requirement_table += (
+ f'| `{requirement}`'
+ f' | {info["text"]}{sensor_info}{detail} |\n'
+ )
+
+ mode_requirements_markdown = f"""# Mode Requirements
+
+::: info
+This documentation was auto-generated from the source code (see [docs/scripts/get_mode_requirements](https://github.com/PX4/PX4-Autopilot/tree/main/docs/scripts/get_mode_requirements)).
+:::
+
+Mode requirements define the set of conditions that must be met in order to arm in a particular flight mode, or to switch to the mode if it is already armed.
+
+Requirements are defined for internal modes in [mode_requirements.cpp](https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/commander/ModeUtil/mode_requirements.cpp), and for ROS 2 external modes in [requirement_flags.hpp](https://github.com/Auterion/px4-ros2-interface-lib/blob/main/px4_ros2_cpp/include/px4_ros2/common/requirement_flags.hpp) (Github `Auterion/px4-ros2-interface-lib` repository).
+The mode requirements are the same in both cases.
+
+The following sections provide an overview of the requirements and what modes they are used in.
+
+## Requirements Definitions
+
+{requirement_table}
+### Naming Conventions
+
+In general requirement flag names are abstracted from specific sensors.
+This is done because particular requirements can often be met by several sensors.
+For example, GNSS is the most common source of global position, but it isn't the only one.
+
+The requirements include frame and accuracy information hints in their names:
+
+- `global` means an absolute world frame, such as that provided by GNSS.
+- `local` means a frame that relative to an initialization point, such as the position of an IMU on boot.
+- `relaxed` means that the mode does not require or rely on accurate data: as long as sensors are providing some data the state is considered valid.
+ Relaxed conditions are used for modes where some sensor data is considered more important than none at all, such as when calculating position via optical flow velocity measurements.
+ By contrast, a position mode that is not relaxed requires reliable sensor data, and will block arming if inaccuracy is detected.
+
+Note that a global position requirement can be met if you have a valid _local position_, by mapping the local frame to a global position.
+This can be done by setting the global position of the local origin using the MAVLink message [SET_GPS_GLOBAL_ORIGIN](https://mavlink.io/en/messages/common.html#SET_GPS_GLOBAL_ORIGIN), either directly or via a GCS (see [External Position Estimate > Enabling Auto Modes with a Local Position](../ros/external_position_estimation#enabling-auto-modes-with-a-local-position)).
+Similarly, if the vehicle has `mode_req_local_position_relaxed`, then you can map to a global position in order to meet the `global_position_relaxed` requirement.
+This allows PX4 automatic flight modes that require a global position to be used locally, such as Mission and Return.
+
+"""
+
+
+
+ for vehicle_type in ordered_vtypes:
+ if vehicle_type == 'VEHICLE_TYPE_ROTARY_WING':
+ mode_requirements_markdown += f"\n## Multicopter ({vehicle_type})\n"
+ elif vehicle_type == 'VEHICLE_TYPE_FIXED_WING':
+ mode_requirements_markdown += f"\n## Fixed-wing ({vehicle_type})\n"
+ else:
+ mode_requirements_markdown += f"\n## {vehicle_type}\n"
+
+
+ undocumented_modes = []
+
+ # Iterate in the order modes are listed in mode_nav_state_defns.json (rather than the
+ # order setRequirement() calls happen to appear in mode_requirements.cpp) so headings
+ # follow the intended low-to-high autonomy progression. Any mode the parser found that
+ # has no entry at all in mode_nav_state_defns.json is appended at the end, preserving
+ # the existing "no entry" warning via _get_nav_state_def().
+ nav_defs_for_vt = mode_nav_state_defns.get(vehicle_type, {})
+ modes_present = vehicle_modes_sorted[vehicle_type]
+ ordered_modes = [m for m in nav_defs_for_vt if m in modes_present]
+ extra_modes = [m for m in modes_present if m not in nav_defs_for_vt]
+
+ for flight_mode in ordered_modes + extra_modes:
+ defn = _get_nav_state_def(vehicle_type, flight_mode)
+ label = defn.get("label")
+ doc = defn.get("doc")
+
+ # Modes with no doc page have no meaningful (or no implemented) behaviour to
+ # document on this vehicle type: list them in a trailing summary instead of
+ # giving them a full heading in the main list.
+ if not doc:
+ undocumented_modes.append((flight_mode, defn.get("status")))
+ continue
+
+ if label:
+ mode_heading_markdown = f"\n### [{label}]({doc}) ({flight_mode})\n\n"
+ else:
+ mode_heading_markdown = f"\n### {flight_mode}\n\n"
+
+ mode_requirements_markdown += mode_heading_markdown
+
+ for requirement in vehicle_modes_sorted[vehicle_type][flight_mode]:
+ #mode_requirements_markdown += f"- {requirement}: {requirement_defns[requirement]}\n"
+ mode_requirements_markdown += f"- [`{requirement}`](#{requirement})\n"
+
+ if undocumented_modes:
+ mode_requirements_markdown += "\n### Modes Without a Dedicated Page\n\n"
+ mode_requirements_markdown += (
+ "The following internal navigation states have no distinct user-facing "
+ "behaviour or documentation page on this frame type:\n\n"
+ )
+ for flight_mode, status in undocumented_modes:
+ if not status:
+ print(
+ f"WARNING: no 'status' text in mode_nav_state_defns.json for "
+ f"({vehicle_type}, {flight_mode}) — it has no doc and will show "
+ "with no explanation in the 'Modes Without a Dedicated Page' summary.",
+ file=sys.stderr,
+ )
+ status = "Not currently documented."
+ mode_requirements_markdown += f"- **{flight_mode}** — {status}\n"
+
+
+ # 2. Define the filename for the overview topic: mode_requirements
+ filename = "mode_requirements.md"
+ filename = docs_output_path_flight_modes / filename
+ try:
+ with open(filename, 'w', encoding='utf-8') as f:
+ # 'w' stands for 'write' mode, which creates the file or overwrites an existing one.
+ f.write(mode_requirements_markdown)
+
+ print(f"✅ Successfully wrote content to {filename}")
+
+ except Exception as e:
+ print(f"❌ An error occurred: {e}")
+
+
+ # Generate per-doc mode requirements, injected inline into parent docs
+
+ snippet_to_parent = build_snippet_to_parent_map(docs_output_path_base)
+
+ # Display labels for vehicle types used in shared-doc headings.
+ _VT_DISPLAY_LABEL = {
+ "VEHICLE_TYPE_FIXED_WING": "Fixed-Wing",
+ "VEHICLE_TYPE_ROTARY_WING": "Multicopter",
+ }
+
+ for vehicle_type in ordered_vtypes:
+ for flight_mode in vehicle_modes_sorted[vehicle_type]:
+ vehicle_part = vehicle_type.split("VEHICLE_TYPE_")[-1]
+ mode_part = flight_mode.split("NAVIGATION_STATE_")[-1]
+
+ snippet_stem = f"mode_requirements_{vehicle_part}_{mode_part}".lower()
+
+ print(f"Processing: {snippet_stem}")
+
+ # Skip modes that have warn=false and no doc in mode_nav_state_defns.json.
+ # This replaces the former INVALID_MODES / NO_DOC_MODES hard-coded sets.
+ nav_defn = mode_nav_state_defns.get(vehicle_type, {}).get(flight_mode, {})
+ if not nav_defn.get("warn", True) and not nav_defn.get("doc"):
+ print(f" Skipping {snippet_stem} (warn=false, no doc in mode_nav_state_defns.json)")
+ continue
+
+ # Resolve parent doc early so we can choose the right heading.
+ parent = snippet_to_parent.get(snippet_stem)
+
+ # Use a labeled heading when injecting into a shared doc (flight_modes/)
+ # so readers know which vehicle type each section applies to.
+ is_shared_doc = parent is not None and parent.parent.name == "flight_modes"
+ if is_shared_doc:
+ vt_label = _VT_DISPLAY_LABEL.get(vehicle_type, vehicle_type)
+ req_heading = f"### Mode Requirements — {vt_label}"
+ else:
+ req_heading = "### Mode Requirements"
+
+ vehicle_mode_markdown = f"""{req_heading}
+
+The following requirements must be met to arm in this mode, or to switch to this mode when it is armed.
+
+"""
+
+ for requirement in vehicle_modes_sorted[vehicle_type][flight_mode]:
+ text = _get_req_def(requirement)['text']
+ vehicle_mode_markdown += f"- [`{requirement}`](../flight_modes/mode_requirements.md#{requirement}) — {text}\n"
+
+ if parent:
+ inline_requirements_into_doc(parent, snippet_stem, vehicle_mode_markdown)
+ else:
+ if not args.suppress_warnings and nav_defn.get("warn", True):
+ print(
+ f"⚠️ Missing mode: '{snippet_stem}' is not mapped to a doc.\n"
+ f" Resolve by:\n"
+ f" • Adding a doc page with an injection point for this mode\n"
+ f" • Setting \"warn\": false in mode_nav_state_defns.json for "
+ f"({vehicle_type}, {flight_mode})\n"
+ f" • Re-running with --suppress_warnings to silence this warning"
+ )
+
+
+
+
+
+
diff --git a/docs/scripts/get_mode_requirements/mode_nav_state_defns.json b/docs/scripts/get_mode_requirements/mode_nav_state_defns.json
new file mode 100644
index 00000000000..fe26b097a0f
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/mode_nav_state_defns.json
@@ -0,0 +1,166 @@
+{
+ "VEHICLE_TYPE_FIXED_WING": {
+ "NAVIGATION_STATE_MANUAL": {
+ "label": "Manual Mode",
+ "doc": "../flight_modes_fw/manual.md"
+ },
+ "NAVIGATION_STATE_STAB": {
+ "label": "Stabilized Mode",
+ "doc": "../flight_modes_fw/stabilized.md"
+ },
+ "NAVIGATION_STATE_ACRO": {
+ "label": "Acro Mode",
+ "doc": "../flight_modes_fw/acro.md"
+ },
+ "NAVIGATION_STATE_ALTCTL": {
+ "label": "Altitude Mode",
+ "doc": "../flight_modes_fw/altitude.md"
+ },
+ "NAVIGATION_STATE_ALTITUDE_CRUISE": {
+ "warn": false,
+ "status": "Behaves identically to [Altitude Mode](../flight_modes_fw/altitude.md) on fixed-wing frames — the control-mode flags are the same and there is no separate implementation."
+ },
+ "NAVIGATION_STATE_POSCTL": {
+ "label": "Cruise Mode",
+ "doc": "../flight_modes_fw/cruise.md"
+ },
+ "NAVIGATION_STATE_POSITION_SLOW": {
+ "warn": false,
+ "status": "Behaves identically to [Position Mode](../flight_modes_fw/position.md) on fixed-wing frames — the control-mode flags are the same and there is no separate implementation."
+ },
+ "NAVIGATION_STATE_GUIDED_COURSE": {
+ "label": "Guided Course Mode",
+ "doc": "../flight_modes_fw/guided_course.md"
+ },
+ "NAVIGATION_STATE_AUTO_LOITER": {
+ "label": "Hold Mode",
+ "doc": "../flight_modes_fw/hold.md"
+ },
+ "NAVIGATION_STATE_ORBIT": {
+ "warn": false,
+ "status": "Not implemented for fixed-wing frames; [Hold Mode](../flight_modes_fw/hold.md) is used instead for orbiting a point."
+ },
+ "NAVIGATION_STATE_AUTO_TAKEOFF": {
+ "label": "Takeoff Mode",
+ "doc": "../flight_modes_fw/takeoff.md"
+ },
+ "NAVIGATION_STATE_AUTO_VTOL_TAKEOFF": {
+ "warn": false,
+ "status": "VTOL-specific transition state used for vertical takeoff before transitioning to forward flight; not applicable to plain fixed-wing frames."
+ },
+ "NAVIGATION_STATE_DESCEND": {
+ "label": "Descend Mode",
+ "doc": "../flight_modes_fw/descend.md"
+ },
+ "NAVIGATION_STATE_AUTO_LAND": {
+ "label": "Land Mode",
+ "doc": "../flight_modes_fw/land.md"
+ },
+ "NAVIGATION_STATE_AUTO_PRECLAND": {
+ "warn": false,
+ "status": "Not implemented for fixed-wing frames (precision landing requires hover capability)."
+ },
+ "NAVIGATION_STATE_AUTO_RTL": {
+ "label": "Return Mode",
+ "doc": "../flight_modes_fw/return.md"
+ },
+ "NAVIGATION_STATE_AUTO_MISSION": {
+ "label": "Mission Mode",
+ "doc": "../flight_modes_fw/mission.md"
+ },
+ "NAVIGATION_STATE_AUTO_FOLLOW_TARGET": {
+ "warn": false,
+ "status": "Not implemented for fixed-wing frames (Follow Me is a multicopter-only flight task)."
+ },
+ "NAVIGATION_STATE_OFFBOARD": {
+ "label": "Offboard Mode",
+ "doc": "../flight_modes_fw/offboard.md"
+ },
+ "NAVIGATION_STATE_TERMINATION": {
+ "warn": false,
+ "status": "Internal flight-termination failsafe state. Not user-selectable; entered automatically when a failsafe action escalates to termination."
+ }
+ },
+ "VEHICLE_TYPE_ROTARY_WING": {
+ "NAVIGATION_STATE_MANUAL": {
+ "label": "Manual/Stabilized Mode",
+ "doc": "../flight_modes_mc/manual_stabilized.md"
+ },
+ "NAVIGATION_STATE_STAB": {
+ "label": "Manual/Stabilized Mode",
+ "doc": "../flight_modes_mc/manual_stabilized.md"
+ },
+ "NAVIGATION_STATE_ACRO": {
+ "label": "Acro Mode",
+ "doc": "../flight_modes_mc/acro.md"
+ },
+ "NAVIGATION_STATE_ALTCTL": {
+ "label": "Altitude Mode",
+ "doc": "../flight_modes_mc/altitude.md"
+ },
+ "NAVIGATION_STATE_ALTITUDE_CRUISE": {
+ "label": "Altitude Cruise Mode",
+ "doc": "../flight_modes_mc/altitude_cruise.md"
+ },
+ "NAVIGATION_STATE_POSCTL": {
+ "label": "Position Mode",
+ "doc": "../flight_modes_mc/position.md"
+ },
+ "NAVIGATION_STATE_POSITION_SLOW": {
+ "label": "Position Slow Mode",
+ "doc": "../flight_modes_mc/position_slow.md"
+ },
+ "NAVIGATION_STATE_GUIDED_COURSE": {
+ "warn": false,
+ "status": "Not implemented for multicopters (course-hold is a fixed-wing-only flight task)."
+ },
+ "NAVIGATION_STATE_AUTO_LOITER": {
+ "label": "Hold Mode",
+ "doc": "../flight_modes_mc/hold.md"
+ },
+ "NAVIGATION_STATE_ORBIT": {
+ "label": "Orbit Mode",
+ "doc": "../flight_modes_mc/orbit.md"
+ },
+ "NAVIGATION_STATE_AUTO_TAKEOFF": {
+ "label": "Takeoff Mode",
+ "doc": "../flight_modes_mc/takeoff.md"
+ },
+ "NAVIGATION_STATE_AUTO_VTOL_TAKEOFF": {
+ "warn": false,
+ "status": "VTOL-specific transition state used for vertical takeoff before transitioning to forward flight; not applicable to plain multicopter frames."
+ },
+ "NAVIGATION_STATE_DESCEND": {
+ "label": "Descend Mode",
+ "doc": "../flight_modes_mc/descend.md"
+ },
+ "NAVIGATION_STATE_AUTO_LAND": {
+ "label": "Land Mode",
+ "doc": "../flight_modes_mc/land.md"
+ },
+ "NAVIGATION_STATE_AUTO_PRECLAND": {
+ "label": "Precision Landing",
+ "doc": "../advanced_features/precland.md"
+ },
+ "NAVIGATION_STATE_AUTO_RTL": {
+ "label": "Return Mode",
+ "doc": "../flight_modes_mc/return.md"
+ },
+ "NAVIGATION_STATE_AUTO_MISSION": {
+ "label": "Mission Mode",
+ "doc": "../flight_modes_mc/mission.md"
+ },
+ "NAVIGATION_STATE_AUTO_FOLLOW_TARGET": {
+ "label": "Follow Me Mode",
+ "doc": "../flight_modes_mc/follow_me.md"
+ },
+ "NAVIGATION_STATE_OFFBOARD": {
+ "label": "Offboard Mode",
+ "doc": "../flight_modes_mc/offboard.md"
+ },
+ "NAVIGATION_STATE_TERMINATION": {
+ "warn": false,
+ "status": "Internal flight-termination failsafe state. Not user-selectable; entered automatically when a failsafe action escalates to termination."
+ }
+ }
+}
diff --git a/docs/scripts/get_mode_requirements/requirement_defns.json b/docs/scripts/get_mode_requirements/requirement_defns.json
new file mode 100644
index 00000000000..bc4689c26a6
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/requirement_defns.json
@@ -0,0 +1,72 @@
+{
+ "mode_req_angular_velocity": {
+ "text": "Angular velocity",
+ "sensor": "gyroscope",
+ "detail": ""
+ },
+ "mode_req_attitude": {
+ "text": "Attitude/pose",
+ "sensor": "IMU, or theoretically a motion capture system",
+ "detail": ""
+ },
+ "mode_req_local_position": {
+ "text": "Position relative to EKF2 origin ('0') point",
+ "sensor": "GNSS, VIO, mocap",
+ "detail": ""
+ },
+ "mode_req_local_position_relaxed": {
+ "text": "Position relative to EKF2 origin ('0') point but accepts poor accuracy",
+ "sensor": "Optical flow",
+ "detail": "Typically optical flow. You set zero when you take off and just integrate up the flow. So the absolute position can be very inaccurate but better than nothing. Useful to not drift away quickly and roughly know where you are."
+ },
+ "mode_req_global_position": {
+ "text": "Position measurement updates in a global coordinate frame",
+ "sensor": "GNSS, or local position and global reference to EKF 0",
+ "detail": ""
+ },
+ "mode_req_global_position_relaxed": {
+ "text": "Position measurement updates in a global coordinate frame but accepts poor accuracy",
+ "sensor": "",
+ "detail": "Local position and global reference to EKF2 origin"
+ },
+ "mode_req_local_alt": {
+ "text": "Local altitude relative to EKF2 origin ('0') position",
+ "sensor": "Barometer corrected by GNSS altitude over time, distance sensor",
+ "detail": "Usually not the distance sensor because if the ground shifts then the coordinate frame would shift with it (range aid problem)."
+ },
+ "mode_req_mission": {
+ "text": "Valid mission in autopilot's storage",
+ "sensor": "Mission mode only requirement",
+ "detail": "can be from last time no need to upload fresh"
+ },
+ "mode_req_offboard_signal": {
+ "text": "Offboard heartbeat",
+ "sensor": "",
+ "detail": "MAVLink messages SET_ATTITUDE_TARGET or SET_POSITION_TARGET_LOCAL_NED or SET_POSITION_TARGET_GLOBAL_INT not timing out. Offboard mode specific requirement."
+ },
+ "mode_req_home_position": {
+ "text": "Global home reference must be set",
+ "sensor": "",
+ "detail": "Specific requirement for Return mode"
+ },
+ "mode_req_wind_and_flight_time_compliance": {
+ "text": "Safety compliance limits on wind and flight time.",
+ "sensor": "",
+ "detail": "Blocks arming or switching into the mode when the estimated wind speed exceeds `COM_WIND_MAX` (if `COM_WIND_MAX_ACT` is set to an action stronger than a warning), or when the flight time exceeds `COM_FLT_TIME_MAX`. Both limits are disabled by default. Modes commanded by a failsafe are exempt (see also `COM_WIND_WARN`)."
+ },
+ "mode_req_prevent_arming": {
+ "text": "Mode prevents arming",
+ "sensor": "",
+ "detail": "The vehicle cannot be armed while this mode is selected. Set for modes such as Land, Orbit, and Return that you can't take off in (they can only be entered in flight)."
+ },
+ "mode_req_manual_control": {
+ "text": "Requires stick input",
+ "sensor": "",
+ "detail": "Manual control can come from an RC system (RC driver -> channel mapping/calibration -> [`manual_control_input`](../msg_docs/ManualControlSetpoint.md)) or from a joystick (MAVLink [MANUAL_CONTROL](https://mavlink.io/en/messages/common.html#MANUAL_CONTROL) message -> `manual_control_input`). A selector configured with [COM_RC_IN_MODE](../advanced_config/parameter_reference.md#COM_RC_IN_MODE) chooses which source is used and publishes it as `manual_control_setpoint`. The requirement is met while the selected source has not timed out."
+ },
+ "mode_req_other": {
+ "text": "Others requirement.",
+ "sensor": "",
+ "detail": "This is used by external modes. It is intended to specify additional requirements not covered by the existing flags"
+ }
+}
diff --git a/docs/scripts/get_mode_requirements/tests/__init__.py b/docs/scripts/get_mode_requirements/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/docs/scripts/get_mode_requirements/tests/test_data/requirement_defns_golden.json b/docs/scripts/get_mode_requirements/tests/test_data/requirement_defns_golden.json
new file mode 100644
index 00000000000..8bfa2812081
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/tests/test_data/requirement_defns_golden.json
@@ -0,0 +1,72 @@
+{
+ "mode_req_angular_velocity": {
+ "text": "Angular velocity",
+ "sensor": "gyroscope",
+ "detail": ""
+ },
+ "mode_req_attitude": {
+ "text": "Attitude/pose",
+ "sensor": "IMU, or theoretically a motion capture system",
+ "detail": ""
+ },
+ "mode_req_local_position": {
+ "text": "Position relative to EKF2 origin ('0') point",
+ "sensor": "GNSS, VIO, mocap",
+ "detail": ""
+ },
+ "mode_req_local_position_relaxed": {
+ "text": "Position relative to EKF2 origin ('0') point but accepts poor accuracy",
+ "sensor": "Optical flow",
+ "detail": "Typically optical flow. You set zero when you take off and just integrate up the flow. So the absolute position can be very inaccurate but better than nothing. Useful to not drift away quickly and roughly know where you are."
+ },
+ "mode_req_global_position": {
+ "text": "Position measurement updates in a global coordinate frame",
+ "sensor": "GNSS, or local position and global reference to EKF 0",
+ "detail": ""
+ },
+ "mode_req_global_position_relaxed": {
+ "text": "Position measurement updates in a global coordinate frame but accepts poor accuracy",
+ "sensor": "",
+ "detail": "Local position and global reference to EKF2 origin"
+ },
+ "mode_req_local_alt": {
+ "text": "Local altitude relative to EKF2 origin ('0') position",
+ "sensor": "Barometer corrected by GNSS altitude over time, distance sensor",
+ "detail": "Usually not the distance sensor because if the ground shifts then the coordinate frame would shift with it (range aid problem)."
+ },
+ "mode_req_mission": {
+ "text": "Valid mission in autopilot's storage",
+ "sensor": "Mission mode only requirement",
+ "detail": "can be from last time no need to upload fresh"
+ },
+ "mode_req_offboard_signal": {
+ "text": "Offboard heartbeat",
+ "sensor": "",
+ "detail": "MAVLink messages SET_ATTITUDE_TARGET or SET_POSITION_TARGET_LOCAL_NED or SET_POSITION_TARGET_GLOBAL_INT not timing out. Offboard mode specific requirement."
+ },
+ "mode_req_home_position": {
+ "text": "Global home reference must be set",
+ "sensor": "",
+ "detail": "Specific requirement for Return mode"
+ },
+ "mode_req_wind_and_flight_time_compliance": {
+ "text": "Safety compliance limits on wind and flight time.",
+ "sensor": "",
+ "detail": "Autonomous flight prevented in high winds (20m/s?) or if exceeds planned flight time, except for failsafe flight. See COM_FLT_TIME_MAX or COM_WIND_MAX for limits (also see COM_WIND_WARN, COM_WIND_MAX_ACT)."
+ },
+ "mode_req_prevent_arming": {
+ "text": "Mode prevents arming (vehicle must be armed to switch to this mode)",
+ "sensor": "",
+ "detail": "Set for modes like Land, Orbit, Return that you can't take off in"
+ },
+ "mode_req_manual_control": {
+ "text": "Requires stick input",
+ "sensor": "",
+ "detail": "This can come from multiple channels: RC driver -> channels -> RC mapping/calibration -> [`manual_control_input` or `manual_control_setpoint` topic](../msg_docs/ManualControlSetpoint.md) -> Selector using [COM_RC_IN_MODE](../advanced_config/parameter_reference.md#COM_RC_IN_MODE) -> Joystick (MAVLink [MANUAL_CONTROL](https://mavlink.io/en/messages/common.html#MANUAL_CONTROL) message) -> ..."
+ },
+ "mode_req_other": {
+ "text": "Others requirement.",
+ "sensor": "",
+ "detail": "This is used by external modes. It is intended to specify additional requirements not covered by the existing flags"
+ }
+}
diff --git a/docs/scripts/get_mode_requirements/tests/test_data/vehicle_modes_golden.json b/docs/scripts/get_mode_requirements/tests/test_data/vehicle_modes_golden.json
new file mode 100644
index 00000000000..255317b797b
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/tests/test_data/vehicle_modes_golden.json
@@ -0,0 +1,257 @@
+{
+ "VEHICLE_TYPE_FIXED_WING": {
+ "NAVIGATION_STATE_MANUAL": [
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_ALTCTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_ALTITUDE_CRUISE": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_POSCTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_POSITION_SLOW": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_AUTO_MISSION": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position_relaxed",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_mission",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_LOITER": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position_relaxed",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_GUIDED_COURSE": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_RTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position_relaxed",
+ "mode_req_home_position",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_ACRO": [
+ "mode_req_angular_velocity",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_DESCEND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_TERMINATION": [
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_OFFBOARD": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_offboard_signal"
+ ],
+ "NAVIGATION_STATE_STAB": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_AUTO_TAKEOFF": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt"
+ ],
+ "NAVIGATION_STATE_AUTO_LAND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_AUTO_FOLLOW_TARGET": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_PRECLAND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_ORBIT": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_VTOL_TAKEOFF": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed"
+ ]
+ },
+ "VEHICLE_TYPE_ROTARY_WING": {
+ "NAVIGATION_STATE_MANUAL": [
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_ALTCTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_ALTITUDE_CRUISE": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_POSCTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_POSITION_SLOW": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_AUTO_MISSION": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_mission",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_LOITER": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_GUIDED_COURSE": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_RTL": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_global_position",
+ "mode_req_home_position",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_ACRO": [
+ "mode_req_angular_velocity",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_DESCEND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_TERMINATION": [
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_OFFBOARD": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_offboard_signal"
+ ],
+ "NAVIGATION_STATE_STAB": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_manual_control"
+ ],
+ "NAVIGATION_STATE_AUTO_TAKEOFF": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position"
+ ],
+ "NAVIGATION_STATE_AUTO_LAND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position_relaxed",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_AUTO_FOLLOW_TARGET": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_PRECLAND": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming"
+ ],
+ "NAVIGATION_STATE_ORBIT": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position",
+ "mode_req_prevent_arming",
+ "mode_req_wind_and_flight_time_compliance"
+ ],
+ "NAVIGATION_STATE_AUTO_VTOL_TAKEOFF": [
+ "mode_req_angular_velocity",
+ "mode_req_attitude",
+ "mode_req_local_alt",
+ "mode_req_local_position"
+ ]
+ }
+}
diff --git a/docs/scripts/get_mode_requirements/tests/test_get_mode_requirements.py b/docs/scripts/get_mode_requirements/tests/test_get_mode_requirements.py
new file mode 100644
index 00000000000..54b6920c044
--- /dev/null
+++ b/docs/scripts/get_mode_requirements/tests/test_get_mode_requirements.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+"""
+Regression tests for get_mode_requirements.py.
+
+Two golden JSON files in test_data/ capture the expected parser output:
+
+ test_data/requirement_defns_golden.json
+ The static requirement_defns mapping (flag name → text/sensor/detail).
+ Update this when adding or editing entries in requirement_defns.
+
+ test_data/vehicle_modes_golden.json
+ The parsed output of mode_requirements.cpp: per-vehicle-type,
+ per-mode lists of requirement flags (sorted alphabetically).
+ Update this when mode_requirements.cpp changes.
+
+Running the tests
+-----------------
+From the get_mode_requirements/ directory:
+ pytest # discover and run all tests
+ pytest tests/ -v # verbose
+
+Updating the golden files after intentional changes
+----------------------------------------------------
+ python tests/test_get_mode_requirements.py --update-golden
+
+This rewrites both golden files with the current parser output and exits.
+
+"""
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+# ---------------------------------------------------------------------------
+# Import the module under test
+# ---------------------------------------------------------------------------
+_TESTS_DIR = Path(__file__).resolve().parent
+_SCRIPT_DIR = _TESTS_DIR.parent # get_mode_requirements/
+
+# Ensure the script directory is on sys.path when run directly.
+# conftest.py does the same when running under pytest.
+if str(_SCRIPT_DIR) not in sys.path:
+ sys.path.insert(0, str(_SCRIPT_DIR))
+
+from get_mode_requirements import parse_requirements, requirement_defns # noqa: E402
+
+# ---------------------------------------------------------------------------
+# Golden file paths
+# ---------------------------------------------------------------------------
+_TEST_DATA = _TESTS_DIR / "test_data"
+_GOLDEN_REQUIREMENT_DEFNS = _TEST_DATA / "requirement_defns_golden.json"
+_GOLDEN_VEHICLE_MODES = _TEST_DATA / "vehicle_modes_golden.json"
+
+
+# ---------------------------------------------------------------------------
+# Helper
+# ---------------------------------------------------------------------------
+
+def _vehicle_modes_as_sorted_lists(vehicle_modes: dict) -> dict:
+ """Convert the sets returned by parse_requirements() to sorted lists."""
+ return {
+ vtype: {mode: sorted(reqs) for mode, reqs in modes.items()}
+ for vtype, modes in vehicle_modes.items()
+ }
+
+
+# ---------------------------------------------------------------------------
+# Tests
+# ---------------------------------------------------------------------------
+
+def test_requirement_defns_matches_golden():
+ """requirement_defns must match the golden snapshot.
+
+ Fails when a flag is added, removed, or its text/sensor/detail is edited
+ without also updating the golden file.
+ """
+ with _GOLDEN_REQUIREMENT_DEFNS.open(encoding="utf-8") as fh:
+ expected = json.load(fh)
+
+ assert requirement_defns == expected, (
+ "requirement_defns has diverged from the golden file.\n"
+ "If this change is intentional run:\n"
+ " python tests/test_get_mode_requirements.py --update-golden"
+ )
+
+
+def test_vehicle_modes_matches_golden():
+ """parse_requirements() applied to the checked-in mode_requirements.cpp
+ must produce output matching the golden snapshot.
+
+ Fails when mode_requirements.cpp is edited in a way that adds, removes, or
+ changes the requirement flags for any mode/vehicle-type combination.
+ """
+ with _GOLDEN_VEHICLE_MODES.open(encoding="utf-8") as fh:
+ expected = json.load(fh)
+
+ vehicle_modes = parse_requirements()
+ actual = _vehicle_modes_as_sorted_lists(vehicle_modes)
+
+ # Collect all differences for a readable failure message
+ diffs = []
+
+ all_vtypes = set(expected) | set(actual)
+ for vtype in sorted(all_vtypes):
+ if vtype not in actual:
+ diffs.append(f" Vehicle type removed: {vtype}")
+ continue
+ if vtype not in expected:
+ diffs.append(f" Vehicle type added: {vtype}")
+ continue
+
+ all_modes = set(expected[vtype]) | set(actual[vtype])
+ for mode in sorted(all_modes):
+ exp_reqs = expected[vtype].get(mode)
+ act_reqs = actual[vtype].get(mode)
+ if exp_reqs != act_reqs:
+ diffs.append(
+ f" {vtype} / {mode}:\n"
+ f" expected: {exp_reqs}\n"
+ f" actual: {act_reqs}"
+ )
+
+ assert not diffs, (
+ "Parsed vehicle modes have diverged from the golden file:\n"
+ + "\n".join(diffs)
+ + "\nIf this change is intentional run:\n"
+ " python tests/test_get_mode_requirements.py --update-golden"
+ )
+
+
+def test_all_requirement_flags_are_defined():
+ """Every requirement flag that appears in parse_requirements() output must
+ have an entry in requirement_defns.
+
+ This catches the case where a new flag is added to mode_requirements.cpp
+ but not documented in requirement_defns.
+ """
+ vehicle_modes = parse_requirements()
+ missing = set()
+ for modes in vehicle_modes.values():
+ for reqs in modes.values():
+ for req in reqs:
+ if req not in requirement_defns:
+ missing.add(req)
+
+ assert not missing, (
+ "The following requirement flags are used in mode_requirements.cpp but "
+ "have no entry in requirement_defns:\n " + "\n ".join(sorted(missing))
+ )
+
+
+# ---------------------------------------------------------------------------
+# Golden-file updater (run directly, not via pytest)
+# ---------------------------------------------------------------------------
+
+def _update_golden():
+ _TEST_DATA.mkdir(exist_ok=True)
+
+ # requirement_defns
+ with _GOLDEN_REQUIREMENT_DEFNS.open("w", encoding="utf-8") as fh:
+ json.dump(requirement_defns, fh, indent=2, ensure_ascii=False)
+ fh.write("\n")
+ print(f"Updated {_GOLDEN_REQUIREMENT_DEFNS}")
+
+ # vehicle_modes
+ vehicle_modes = parse_requirements()
+ vehicle_modes_sorted = _vehicle_modes_as_sorted_lists(vehicle_modes)
+ with _GOLDEN_VEHICLE_MODES.open("w", encoding="utf-8") as fh:
+ json.dump(vehicle_modes_sorted, fh, indent=2, ensure_ascii=False)
+ fh.write("\n")
+ print(f"Updated {_GOLDEN_VEHICLE_MODES}")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Manage golden files for get_mode_requirements tests.")
+ parser.add_argument(
+ "--update-golden",
+ action="store_true",
+ help="Rewrite the golden JSON files with the current parser output.",
+ )
+ args = parser.parse_args()
+
+ if args.update_golden:
+ _update_golden()
+ else:
+ parser.print_help()