Claude Cursor Skill

gazebo

Build and debug modern Gazebo simulations and their ROS 2 boundary.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download robium-ai-robium-skills_gazebo-498ea4e.zip · 16 KB
Part of robium-ai/robium — 44 skills

Install

skills CLI npx skills add https://github.com/robium-ai/robium/tree/main/skills/gazebo
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
Git git clone https://github.com/robium-ai/robium.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole robium-ai/robium collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Gazebo

Treat a Gazebo simulation as a contract between world, robot, sensors, transport, and time. Find the first contract that is not producing believable evidence.

Start from the actual stack

  • For a new robotics app or first demo, read architect before creating worlds, launchers, or containers. Reuse its compatible reference-app selection if already made. Existing-world edits, simulator debugging, and explanations stay here.
  • Confirm the installed gz release, ROS distro, ros_gz pairing, render backend, and whether the run is graphical or headless.
  • Use modern Gazebo (gz). Gazebo Classic and libgazebo_ros_* tutorials are a different, end-of-life stack.
  • Keep upstream compatibility tables and SDF specifications as the authority for release-sensitive package names, tags, and plugin APIs.

Trace the simulation boundary

  • World: the selected world loads and the expected systems are present.
  • Robot: links, joints, frames, and drive plugins agree with the physical model and downstream interfaces.
  • Sensors: rates, fields of view, ranges, noise, frames, and timestamps model the intended hardware rather than tutorial defaults.
  • Transport: prove the Gazebo topic exists before debugging its ROS bridge.
  • Bridge: keep a reviewable YAML bridge configuration for a real app; ad-hoc bridge commands are suitable only for diagnosis.
  • ROS: confirm /clock, commands, odometry, transforms, and sensor messages arrive with the expected direction and QoS.

Do not infer simulation correctness from a running process or visible GUI. A render-backed sensor may be silent even while physics continues.

Go deeper only when needed

  • For SDF worlds, models, drive plugins, spawning, and headless server mode, read worlds and models.
  • For lidar, camera, IMU, contact, noise, and rendering, read sensors.
  • For ros_gz_bridge, topic direction, types, QoS, /clock, and frame overrides, read the ROS 2 bridge guide.
  • For a silent world, sensor, bridge, or remote launch, read failures.
  • Use the bundled SDF and bridge YAML together only as a starting pair; keep their topic and frame names synchronized and verify syntax upstream.

Move to navigation only after the robot pose, sensor, map, and velocity interfaces are valid. Move to ros2 when Gazebo is publishing correctly and the fault is in the ROS graph. Use environments for container GPU/display setup and simulation only when the simulator itself has not been chosen.

Done

  • Physics time advances and the expected world and model are present.
  • Every required Gazebo topic has a matching, correctly directed ROS interface.
  • Sensor rate, frame, timestamp, and render output are believable.
  • The run works in its intended graphical or headless environment.
  • Release-specific claims match current Gazebo and ros_gz documentation.
Files (robium)
  • examples
    • diffdrive-world-snippet.sdf 10.9 KB · in bundle
    • ros-gz-bridge-config.yaml 3.2 KB
      # status: unverified
      # Format sourced directly from gazebosim/ros_gz's ros_gz_bridge/README.md
      # ("Example 5: Configuring the Bridge via YAML"), fetched via raw GitHub
      # curl on 2026-07-10; every field name here (ros_topic_name, gz_topic_name,
      # ros_type_name, gz_type_name, direction, qos_profile, frame_id) is copied
      # from that file's own documented shape, not invented.
      #
      # Pairs with ../examples/diffdrive-world-snippet.sdf: every gz_topic_name
      # below matches, character-for-character, a <topic>/<odom_topic>/<tf_topic>
      # set explicitly in that world's DiffDrive plugin, or a sensor's <topic>
      # tag; gz-transport topic matching is exact-string (a leading slash on one
      # side and not the other, e.g. "cmd_vel" vs. "/cmd_vel", is a silent
      # non-match, not a typo Gazebo warns about). Renaming a topic in one file
      # without the other silently breaks that bridge entry; see the "Bridge
      # bridge shape in ../references/ros2-bridge.md.
      #
      # frame_id overrides below are set to "base_link" for scan/imu because the
      # example world mounts both sensors at base_link's own origin with no
      # separate offset frame (see that file's header for why); a robot with an
      # offset sensor frame should instead publish a static base_link -> sensor
      # transform (the `ros2` skill's territory) and drop these overrides so the
      # sensor's own frame_id survives the bridge.
      #
      # Run with:
      #   ros2 run ros_gz_bridge parameter_bridge \
      #     --ros-args -p config_file:=ros-gz-bridge-config.yaml
      
      # Gazebo is the sole /clock source once bridged; see
      # ../references/ros2-bridge.md's "/clock needs special handling" section.
      # Every use_sim_time:=true ROS 2 node (including Nav2) depends on this.
      - ros_topic_name: "/clock"
        gz_topic_name: "/clock"
        ros_type_name: "rosgraph_msgs/msg/Clock"
        gz_type_name: "gz.msgs.Clock"
        direction: GZ_TO_ROS
      
      # Velocity commands into the sim (matches the DiffDrive plugin's
      # <topic>/cmd_vel</topic> in the paired world file).
      - ros_topic_name: "/cmd_vel"
        gz_topic_name: "/cmd_vel"
        ros_type_name: "geometry_msgs/msg/Twist"
        gz_type_name: "gz.msgs.Twist"
        direction: ROS_TO_GZ
      
      # Wheel odometry out of the sim (matches <odom_topic>/odom</odom_topic>).
      # The navigation configuration and robot model must agree with the "odom" /
      # "base_link" frames set by the paired world's DiffDrive plugin.
      - ros_topic_name: "/odom"
        gz_topic_name: "/odom"
        ros_type_name: "nav_msgs/msg/Odometry"
        gz_type_name: "gz.msgs.Odometry"
        direction: GZ_TO_ROS
      
      # odom -> base_link transform (matches <tf_topic>/tf</tf_topic>).
      - ros_topic_name: "/tf"
        gz_topic_name: "/tf"
        ros_type_name: "tf2_msgs/msg/TFMessage"
        gz_type_name: "gz.msgs.Pose_V"
        direction: GZ_TO_ROS
      
      # Lidar scan (matches the gpu_lidar sensor's <topic>/scan</topic>). Bridge it to
      # the same ROS topic selected by the installed localization configuration.
      - ros_topic_name: "/scan"
        gz_topic_name: "/scan"
        ros_type_name: "sensor_msgs/msg/LaserScan"
        gz_type_name: "gz.msgs.LaserScan"
        direction: GZ_TO_ROS
        qos_profile: SENSOR_DATA
        frame_id: "base_link"
      
      # IMU (matches the imu sensor's <topic>/imu</topic>).
      - ros_topic_name: "/imu"
        gz_topic_name: "/imu"
        ros_type_name: "sensor_msgs/msg/Imu"
        gz_type_name: "gz.msgs.IMU"
        direction: GZ_TO_ROS
        qos_profile: SENSOR_DATA
        frame_id: "base_link"
      
  • references
    • ros2-bridge.md 5.6 KB
      # The ros_gz bridge
      
      `ros_gz_bridge`'s `parameter_bridge` exchanges messages between ROS 2 topics/
      services and Gazebo Transport topics/services. Everything below is sourced
      directly from the `gazebosim/ros_gz` repo's `ros_gz_bridge/README.md`
      (`ros2` branch), fetched via raw GitHub `curl` on 2026-07-10; the CLI
      syntax, the YAML field list, and the message-type table are copied from that
      file's own examples, not reconstructed from memory or search snippets.
      
      ## ROS 2 / Gazebo pairing (verify before installing)
      
      From the same repo's top-level `README.md`, fetched directly on 2026-07-10:
      
      | ROS 2 | Gazebo | Binaries |
      |---|---|---|
      | Jazzy | Harmonic | packages.ros.org (`jazzy` branch) |
      | Jazzy | Garden | source only; Garden is EOL |
      | Kilted | Ionic | packages.ros.org |
      | Lyrical | Jetty | packages.ros.org |
      | Lyrical | Harmonic / Ionic | source only |
      | Rolling | Jetty | packages.ros.org |
      | Rolling | Harmonic / Ionic | source only |
      
      The current `navigation` skill's proven TurtleBot3 profile targets Jazzy and
      Harmonic, so this reference's navigation examples use that row with prebuilt
      binaries (`sudo apt-get install ros-jazzy-ros-gz`). Re-fetch this table before
      picking a pairing for a different ROS 2 distro; it changes with every new
      named Gazebo or ROS 2 release.
      
      ## Ad-hoc CLI bridging (one-off use only)
      
      ```bash
      ros2 run ros_gz_bridge parameter_bridge \
        /chatter@std_msgs/msg/String@gz.msgs.StringMsg
      ```
      
      The `@` syntax is bidirectional; `[` (ROS→GZ dropped, i.e. GZ→ROS only) or
      `]` (ROS→GZ only) replace the middle `@` for a unidirectional bridge, e.g.
      `/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock` for the one-way `/clock`
      bridge Gazebo itself recommends (see below). This skill's key directives
      reserve ad-hoc CLI bridges for quick tests; a real project's bridge set
      belongs in a YAML config file (below).
      
      ## YAML config file (the recommended approach)
      
      Pass a YAML file via the `config_file` ROS parameter:
      
      ```bash
      ros2 run ros_gz_bridge parameter_bridge \
        --ros-args -p config_file:=path/to/bridge.yaml
      ```
      
      The file is a YAML array of maps, one per bridge. Every field below is
      copied from the README's own "Example 5" (`ros_gz_bridge/test/config/
      full.yaml`'s documented shape):
      
      ```yaml
      - ros_topic_name: "ros_chatter"
        gz_topic_name: "gz_chatter"
        ros_type_name: "std_msgs/msg/String"
        gz_type_name: "gz.msgs.StringMsg"
        subscriber_queue: 5       # default 10 if qos_profile is empty
        publisher_queue: 6        # default 10 if qos_profile is empty
        lazy: true                # default false; only bridge while someone's subscribed
        direction: BIDIRECTIONAL  # or GZ_TO_ROS / ROS_TO_GZ
        qos_profile: SENSOR_DATA  # default: a default-constructed QoS
        frame_id: "map"           # optional: override the ROS message header's frame_id
      ```
      
      `topic_name` sets both sides at once if `ros_topic_name`/`gz_topic_name`
      aren't given separately. `direction` defaults to `BIDIRECTIONAL`; sensor
      data and `/clock` should be `GZ_TO_ROS`, and command topics like `/cmd_vel`
      should be `ROS_TO_GZ`; see `examples/ros-gz-bridge-config.yaml`.
      
      YAML config does not support launch-time substitutions (e.g. a
      parametrized world/robot name); use the XML or Python launch-file forms
      (`<ros_gz_bridge>` tags, or a `parameter_bridge` `Node` with
      `bridges.<name>.<setting>` parameters) instead when that's needed; both are
      documented in the same README ("Example 6"/"Example 7") and can be combined
      with a YAML `config_file` on the same bridge instance.
      
      ## Message type table (subset relevant to this skill)
      
      Full table is much longer (services, geometry, actuator types); this is the
      subset this skill's examples use, copied verbatim from the README:
      
      | ROS type | Gazebo Transport type |
      |---|---|
      | `rosgraph_msgs/msg/Clock` | `gz.msgs.Clock` |
      | `geometry_msgs/msg/Twist` | `gz.msgs.Twist` |
      | `nav_msgs/msg/Odometry` | `gz.msgs.Odometry` |
      | `tf2_msgs/msg/TFMessage` | `gz.msgs.Pose_V` |
      | `sensor_msgs/msg/LaserScan` | `gz.msgs.LaserScan` |
      | `sensor_msgs/msg/Imu` | `gz.msgs.IMU` |
      | `sensor_msgs/msg/Image` | `gz.msgs.Image` |
      | `sensor_msgs/msg/CameraInfo` | `gz.msgs.CameraInfo` |
      
      See the upstream README for the complete table (batteries, GPS, point
      clouds, detections, and more) before bridging a type not listed here.
      
      ## `/clock` needs special handling
      
      If Gazebo detects another `/clock` publisher at startup, it falls back to a
      fully-qualified `/world/<world>/clock` topic instead, so bridge `/clock`
      explicitly and unidirectionally (`GZ_TO_ROS`) so Gazebo stays the sole
      `/clock` source: every `use_sim_time:=true` node (Nav2 included; see the
      `navigation` skill's TurtleBot3 compatibility card) depends on this topic
      actually publishing, or every TF/action timestamp check hangs waiting for a
      clock that never arrives.
      
      ## `frame_id` / `override_frame_id`
      
      Two related mechanisms, both from the README: the YAML config's `frame_id`
      field (shown above) overrides any bridged message's header `frame_id`. The
      bridge node's `override_frame_id` ROS parameter does the same thing for a
      single ad-hoc CLI-bridged topic, e.g.
      `--ros-args -p override_frame_id:=my_optical_frame`. Cameras are the most
      common case: Gazebo's camera frame convention isn't ROS's z-forward
      optical-frame convention (REP-103), so bridged `Image`/`CameraInfo` topics
      typically need this set to whatever optical frame a static transform
      publishes.
      
      ## Other bridge parameters
      
      `subscription_heartbeat` (default 1000 ms; how often the bridge checks for
      new subscribers on `lazy` bridges) and `expand_gz_topic_names` (default
      `false`; apply the bridge node's ROS namespace to the Gazebo-side topic
      name too, needed when spawning multiple namespaced robots in one world).
      
    • sensors.md 6.2 KB
      # Sensors
      
      SDF sensor tags for IMU, contact, lidar, and camera, the system plugins each
      depends on, and the shared `<noise>` element. All tags below were verified
      on 2026-07-10 by direct `curl` of the raw source files cited per section;
      re-verify before trusting a specific default in a real project.
      
      ## Shared pattern
      
      Every sensor is a `<sensor name="..." type="...">` element inside a
      `<link>`, with common tags `<always_on>` (update per `<update_rate>` if
      true), `<update_rate>` (Hz), `<visualize>` (show in the GUI), and `<topic>`
      (the gz topic data is published on; set explicitly for a flat, predictable
      name rather than relying on the model-scoped default). Source: Gazebo's
      Sensors tutorial (`harmonic/sensors.md`, `gazebosim/docs` repo, fetched
      directly on 2026-07-10; the tutorial that ships with the Harmonic release
      this skill's nav-facing examples target).
      
      ## IMU
      
      Requires the `gz-sim-imu-system` world plugin (`gz::sim::systems::Imu`).
      Outputs `orientation` (quaternion), `angular_velocity`, and
      `linear_acceleration`:
      
      ```xml
      <!-- under <world> -->
      <plugin filename="gz-sim-imu-system" name="gz::sim::systems::Imu" />
      
      <!-- under a <link> -->
      <sensor name="imu_sensor" type="imu">
        <always_on>1</always_on>
        <update_rate>100</update_rate>
        <visualize>true</visualize>
        <topic>imu</topic>
      </sensor>
      ```
      
      No GPU/render engine dependency; IMU is computed directly from the physics
      engine's link state.
      
      ## Contact
      
      Requires `gz-sim-contact-system`. Reports when a named collision touches
      another:
      
      ```xml
      <plugin filename="gz-sim-contact-system" name="gz::sim::systems::Contact" />
      
      <sensor name="sensor_contact" type="contact">
        <contact>
          <collision>collision</collision>
        </contact>
      </sensor>
      ```
      
      Commonly paired with `gz-sim-touchplugin-system` (fires when a named
      `<target>` model touches the sensor's owner, publishing to
      `<namespace>/touched`) and `gz-sim-triggered-publisher-system` (publishes a
      configured message when an input topic matches a pattern, e.g. stop the
      robot on contact). No GPU dependency.
      
      ## Lidar
      
      Use the current preferred type/tag `type="gpu_lidar"` with a `<lidar>`
      element; `type="ray"`/`type="gpu_ray"` with a `<ray>` element are legacy
      aliases, structurally identical but flagged for eventual deprecation in the
      SDF spec (source: `sdformat`'s `sensor.sdf` schema file, fetched directly
      on 2026-07-10: "It is preferred to use ... 'lidar', 'gpu_lidar' ... since
      'ray', 'gpu_ray' ... will be deprecated"). Requires the `gz-sim-sensors-
      system` world plugin with an explicit render engine (GPU-rendered, unlike
      IMU/contact):
      
      ```xml
      <!-- under <world> -->
      <plugin filename="gz-sim-sensors-system" name="gz::sim::systems::Sensors">
        <render_engine>ogre2</render_engine>
      </plugin>
      
      <!-- under a <link> -->
      <sensor name="gpu_lidar" type="gpu_lidar">
        <topic>scan</topic>
        <update_rate>10</update_rate>
        <lidar>
          <scan>
            <horizontal>
              <samples>640</samples>
              <resolution>1</resolution>
              <min_angle>-1.396263</min_angle>
              <max_angle>1.396263</max_angle>
            </horizontal>
            <vertical>
              <samples>1</samples>
              <resolution>0.01</resolution>
              <min_angle>0</min_angle>
              <max_angle>0</max_angle>
            </vertical>
          </scan>
          <range>
            <min>0.08</min>
            <max>10.0</max>
            <resolution>0.01</resolution>
          </range>
          <noise>
            <type>gaussian</type>
            <mean>0.0</mean>
            <stddev>0.01</stddev>
          </noise>
        </lidar>
        <always_on>1</always_on>
        <visualize>true</visualize>
      </sensor>
      ```
      
      `<samples>`/`<resolution>` under `<horizontal>`/`<vertical>` control ray
      count and interpolation; `<range>` bounds each ray's min/max distance and
      linear resolution. Source: Gazebo's Sensors tutorial for the `<lidar>`
      child tags, `sdformat`'s `lidar.sdf` schema file for the authoritative
      element list including `<noise>` (both fetched directly on 2026-07-10; the
      tutorial itself still shows the legacy `<ray>` tag; this reference
      recommends `<lidar>` per the schema's own deprecation note above).
      
      ## Camera
      
      Also requires `gz-sim-sensors-system` (GPU-rendered):
      
      ```xml
      <sensor name="camera" type="camera">
        <camera>
          <horizontal_fov>1.047</horizontal_fov>
          <image>
            <width>640</width>
            <height>480</height>
          </image>
          <clip>
            <near>0.1</near>
            <far>100</far>
          </clip>
        </camera>
        <always_on>1</always_on>
        <update_rate>30</update_rate>
        <visualize>true</visualize>
        <topic>camera</topic>
      </sensor>
      ```
      
      Source: `gz-sim`'s `examples/worlds/camera_sensor.sdf`, fetched directly via
      raw GitHub on 2026-07-10. Camera images bridge to ROS through `ros_gz_image`
      (unidirectional, `image_transport`-based) or `ros_gz_bridge`'s
      `sensor_msgs/msg/Image` mapping; see `references/ros2-bridge.md`. The
      bridge's `override_frame_id` parameter is the documented way to point a
      bridged `Image`/`CameraInfo` message's `frame_id` at a proper z-forward
      optical frame (REP-103), since Gazebo's own camera frame convention differs
      from ROS's.
      
      ## Noise (shared element)
      
      The `<noise type="...">` element (`sdformat`'s `noise.sdf` schema, fetched
      directly on 2026-07-10) is the general form used across sensor types beyond
      the lidar-specific block shown above:
      
      | Tag | Applies to `type="gaussian*"` |
      |---|---|
      | `<mean>` | mean of the sampled noise value |
      | `<stddev>` | standard deviation of the sampled noise value |
      | `<bias_mean>` / `<bias_stddev>` | a per-run constant bias, itself drawn from a Gaussian |
      | `<dynamic_bias_stddev>` / `<dynamic_bias_correlation_time>` | slow bias drift over time (e.g. IMU gyro drift) |
      
      `type="gaussian_quantized"` additionally rounds outputs to a `<precision>`
      step. A sensor left at the tutorial/example defaults (frequently zero
      noise) will not expose the same failure modes a downstream consumer will hit
      against the real sensor; set noise from the real sensor's datasheet and use
      the `simulation` skill's sensor-fidelity card when transfer matters.
      
      ## GPU dependency and headless rendering
      
      IMU and contact sensors have no render-engine dependency. Camera and
      `gpu_lidar` sensors do, via `gz-sim-sensors-system`'s render engine (OGRE2
      by default); see this skill's [failure router](../FAILURES.md) for headless-rendering and
      GPU-vs-software-rendering guidance when running these sensor types without a
      display or GPU.
      
    • worlds-and-models.md 7 KB
      # Worlds and models
      
      SDF (Simulation Description Format) world/model anatomy for modern Gazebo
      (`gz`), how to spawn a model into a running simulation from ROS 2, and how
      to run headless. All tag names, plugin filenames, and CLI flags below were
      verified on 2026-07-10 by direct `curl` of the raw source files cited in each
      section; re-verify before trusting a specific value in a real project, per
      the source-of-truth rule in this skill's entrypoint.
      
      ## Release/pairing status (verify before using)
      
      Fetched directly via `curl https://gazebosim.org/docs/all/releases/` this
      session (HTML stripped, table read from the rendered text, not AI-summarized
      search snippets):
      
      | Codename | Start    | EOL      | Support     |
      |----------|----------|----------|-------------|
      | Jetty    | Sep 2025 | May 2031 | LTS         |
      | Ionic    | Sep 2024 | Dec 2026 | Standard    |
      | Harmonic | Sep 2023 | May 2029 | LTS         |
      | Fortress | Sep 2021 | May 2027 | LTS         |
      | Garden   | Sep 2022 | Nov 2024 | EOL         |
      
      Re-fetch this page before starting a new project; Gazebo ships a new named
      release roughly every year, and the "current LTS" answer shifts each time
      (Jetty superseded Harmonic/Ionic as the newest LTS after the original
      reference was written).
      
      ## SDF world structure
      
      An SDF file's root is `<sdf version="...">`, containing one or more
      `<world name="...">` elements. A world needs at minimum a physics engine and
      the scene-broadcaster system plugin to be usable from the GUI or `gz topic`;
      adding *any* `<plugin>` to a world that had none disables Gazebo's implicit
      defaults, so an explicit minimal set is required once you add your first
      plugin (source: Gazebo's Sensors tutorial, `harmonic/sensors.md`, fetched
      directly on 2026-07-10):
      
      ```xml
      <sdf version="1.9">
        <world name="demo">
          <plugin filename="gz-sim-physics-system"
                  name="gz::sim::systems::Physics" />
          <plugin filename="gz-sim-scene-broadcaster-system"
                  name="gz::sim::systems::SceneBroadcaster" />
          <plugin filename="gz-sim-user-commands-system"
                  name="gz::sim::systems::UserCommands" />
          <!-- add gz-sim-sensors-system if the world has camera/gpu_lidar
               sensors; see references/sensors.md -->
        </world>
      </sdf>
      ```
      
      `<physics name="1ms" type="ignored">` with `<max_step_size>`/
      `<real_time_factor>` children controls the simulation step; a
      `<light type="directional">` and a static `ground_plane` model are the usual
      minimum for a visually sane world (source: `gazebosim/docs`'
      `harmonic/tutorials/moving_robot/moving_robot.sdf`, fetched directly this
      session).
      
      ## Model, link, and joint anatomy
      
      A `<model name="..." canonical_link="...">` contains `<link>` elements
      (each with `<inertial>`, `<visual>`, `<collision>`), `<joint>` elements
      connecting pairs of links (`<parent>`/`<child>`, an `<axis>` with `<limit>`
      for revolute joints), and optional `<frame>` elements for named offset poses
      (e.g. where a sensor attaches) without adding an extra link. Poses
      (`<pose relative_to="...">`) are relative by default to the model's own
      frame unless `relative_to` names another link or frame; this is how a
      sensor mounted on `chassis` gets positioned without redundant math. See
      `examples/diffdrive-world-snippet.sdf` for a worked single-link example;
      the upstream `moving_robot.sdf` (cited above) shows the fuller
      chassis+two-wheels+caster pattern this example is adapted from.
      
      ## Referencing models with `<include>`
      
      `<include><uri>...</uri></include>` pulls in a model either from a local
      path, a Fuel URL
      (`https://fuel.gazebosim.org/1.0/<owner>/models/<name>`), or a
      `package://<ros_pkg>/<path>` URI once a ROS package exports its model
      directory via `package.xml`'s `<export><gazebo_ros
      gazebo_model_path="${prefix}/../"/></export>` tag (source: `ros_gz_sim`
      README, fetched directly on 2026-07-10; see the tag's exact semantics
      there, including the `${prefix}` expansion to the package's installed share
      path).
      
      ## The `DiffDrive` system plugin
      
      `gz-sim-diff-drive-system` (`gz::sim::systems::DiffDrive`) is the standard
      differential-drive controller. Parameters (source: `gz-sim`'s
      `DiffDrive.hh` header comment, fetched directly via raw GitHub this
      session; this is the plugin's own doc, not a summary):
      
      | Tag | Default | Notes |
      |---|---|---|
      | `<left_joint>` / `<right_joint>` | (none) | required, repeatable for multi-wheel |
      | `<wheel_separation>` | 1.0 m | |
      | `<wheel_radius>` | 0.2 m | |
      | `<odom_publish_frequency>` | 50 Hz | |
      | `<topic>` | `/model/<model>/cmd_vel` | set explicitly for a flat, unscoped name |
      | `<odom_topic>` | `/model/<model>/odometry` | |
      | `<tf_topic>` | `/model/<model>/tf` | publishes `gz.msgs.Pose_V` |
      | `<frame_id>` | `<model>/odom` | the odometry/TF parent frame |
      | `<child_frame_id>` | `<model>/<link>` | the odometry/TF child frame |
      | `<min_velocity>`/`<max_velocity>`, `*_acceleration`, `*_jerk` | unset | optional linear/angular limits, each overridable per-axis |
      
      Setting `<topic>/cmd_vel</topic>`, `<odom_topic>/odom</odom_topic>`,
      `<frame_id>odom</frame_id>`, and `<child_frame_id>base_link</child_frame_id>`
      explicitly (rather than leaving the model-scoped defaults) is what makes a
      single-robot world's gz topics and TF frames line up with what Nav2
      expects (`odom`→`base_link`) without a namespace prefix; see
      `examples/diffdrive-world-snippet.sdf`.
      
      ## Spawning a robot from SDF/URDF
      
      `ros_gz_sim`'s `create` executable (source: `ros_gz_sim/src/create.cpp`,
      fetched directly on 2026-07-10; flags read from its `gflags::DEFINE_*`
      declarations, not inferred) spawns an entity via a ROS 2 process rather than
      a raw Gazebo Transport service call:
      
      ```bash
      ros2 run ros_gz_sim create -world default -file /path/to/model.sdf \
        -name my_robot -x 0 -y 0 -z 0.1
      ```
      
      Flags: `-world`, `-file` (path or Fuel URL), `-param` (load XML from a ROS
      parameter), `-string` (load XML from a literal string), `-topic` (subscribe
      to a latched `std_msgs/msg/String` publisher; the pattern for spawning a
      URDF that `robot_state_publisher` already published to
      `/robot_description`), `-name`, `-allow_renaming`, and `-x`/`-y`/`-z`/`-R`/
      `-P`/`-Y` for the initial pose. `ros2 run ros_gz_sim create --helpshort`
      prints the live flag list; check it against a specific `ros_gz` version
      before scripting around it.
      
      Both input shapes occur in current vendor applications: TurtleBot3 supplies a
      model with `-file`, while TurtleBot4 publishes `robot_description` and spawns
      with `-topic`. Choose from the model ownership boundary rather than treating
      one form as merely illustrative.
      
      ## Running headless
      
      `gz sim -s -r <world>.sdf`: `-s` runs the server only ("headless mode",
      overriding `-g` if also given) and `-r` starts the simulation running
      immediately rather than paused. Add `-v 4` for debug-level console output,
      `--headless-rendering` if the world has GPU-rendered sensors and there is no
      display (see `references/sensors.md`), and `--record` (or
      `--record-path <dir>`) to log state for later playback. Source: `gz-sim`'s
      own CLI help text, `src/cmd/cmdsim.rb.in`, fetched directly via raw GitHub
      on 2026-07-10; this is the authoritative flag list, not a tutorial's subset
      of it.
      
  • FAILURES.md 3.1 KB
    # Gazebo failures
    
    Use the symptom to locate the failed simulation contract.
    
    - **`gz` is not found in a ROS image**
      - Some ROS packages vendor the CLI beneath `/opt/ros/<distro>`; check that the
        ROS environment is sourced before installing another Gazebo.
      - `gz stats` belongs to Gazebo Classic. For modern Gazebo, inspect the world's
        stats topic and confirm syntax with the current CLI help.
    
    - **The server runs but camera or lidar data is absent**
      - Camera and `gpu_lidar` depend on the render engine even in server-only mode.
      - Check OGRE2/EGL or the software renderer and use the current headless-
        rendering option when no display is present.
      - Inspect vendor robot xacro and included model files before adding a Sensors
        system to the world. In the 2026-09-07 Silly TurtleBot Jazzy container, the
        Create 3 model already owned a Sensors system fixed to Ogre 1; adding a
        second Ogre2 system produced duplicate scene/HLMS failures. Patching that
        exact installed xacro to Ogre2 while retaining one Sensors system restored
        a roughly 3.5 Hz simulated OAK-D stream. Treat this as package-specific
        evidence and re-inspect the installed source before applying it elsewhere.
      - Physics or odometry continuing does not prove render-backed sensors work.
    
    - **A vendor demo always opens a GUI**
      - Some vendor top-level launch files hardcode a client or do not expose
        headless arguments. In Robium's 2026-07-11 TurtleBot3/Jazzy trial and a
        2026-08-02 TurtleBot4 source review, this happened through two different
        launch structures.
      - Compose the upstream Gazebo server launch with explicit headless arguments
        and reuse the vendor's parameterized spawn, state-publisher, and bridge
        sub-launches rather than copying the entire top-level file.
    
    - **Gazebo topics exist but ROS topics do not**
      - Compare the exact Gazebo topic, ROS topic, message types, direction, and
        bridge configuration.
      - Treat `/clock` separately; without a healthy clock, downstream nodes using
        simulation time can look like bridge or TF failures.
    
    - **Clients repeat `Requesting list of world names.`**
      - Gazebo Transport discovery uses UDP multicast; cloud sandboxes, container
        networks, and some VPNs may drop it.
      - On a same-host deployment, Robium's 2026-07-12 Cloud Run trial restored
        unicast discovery with `GZ_RELAY=127.0.0.1` and `GZ_IP=127.0.0.1`.
        Multi-host setups require a real peer address and a network design.
      - That trial also found a sticky per-boot `SO_REUSEPORT` race: relayed
        announcements reached one of several local sockets and a failed boot did
        not recover. A roughly 120-second no-data watchdog that recycled the
        instance was more effective than an in-process retry loop. These values are
        evidence from those Cloud Run conditions, not universal defaults.
    
    - **It works natively on macOS but not with ROS**
      - Gazebo itself may have a native macOS package; the `ros_gz` path still
        requires a ROS 2 environment, which normally means Linux or a container.
    
    For exact headless flags and bridge fields, use the current CLI help and the
    official documentation linked from the main skill.
    
  • SKILL.md 3.1 KB
    ---
    name: gazebo
    description: Build and debug modern Gazebo simulations and their ROS 2 boundary.
    ---
    
    # Gazebo
    
    Treat a Gazebo simulation as a contract between world, robot, sensors,
    transport, and time. Find the first contract that is not producing believable
    evidence.
    
    ## Start from the actual stack
    
    - For a new robotics app or first demo, read
      [architect](../architect/SKILL.md) before creating worlds, launchers, or
      containers. Reuse its compatible reference-app selection if already made.
      Existing-world edits, simulator debugging, and explanations stay here.
    - Confirm the installed `gz` release, ROS distro, `ros_gz` pairing, render
      backend, and whether the run is graphical or headless.
    - Use modern Gazebo (`gz`). Gazebo Classic and `libgazebo_ros_*` tutorials are
      a different, end-of-life stack.
    - Keep upstream compatibility tables and SDF specifications as the authority
      for release-sensitive package names, tags, and plugin APIs.
    
    ## Trace the simulation boundary
    
    - **World:** the selected world loads and the expected systems are present.
    - **Robot:** links, joints, frames, and drive plugins agree with the physical
      model and downstream interfaces.
    - **Sensors:** rates, fields of view, ranges, noise, frames, and timestamps
      model the intended hardware rather than tutorial defaults.
    - **Transport:** prove the Gazebo topic exists before debugging its ROS bridge.
    - **Bridge:** keep a reviewable YAML bridge configuration for a real app;
      ad-hoc bridge commands are suitable only for diagnosis.
    - **ROS:** confirm `/clock`, commands, odometry, transforms, and sensor messages
      arrive with the expected direction and QoS.
    
    Do not infer simulation correctness from a running process or visible GUI. A
    render-backed sensor may be silent even while physics continues.
    
    ## Go deeper only when needed
    
    - For SDF worlds, models, drive plugins, spawning, and headless server mode,
      read [worlds and models](references/worlds-and-models.md).
    - For lidar, camera, IMU, contact, noise, and rendering, read
      [sensors](references/sensors.md).
    - For `ros_gz_bridge`, topic direction, types, QoS, `/clock`, and frame
      overrides, read [the ROS 2 bridge guide](references/ros2-bridge.md).
    - For a silent world, sensor, bridge, or remote launch, read
      [failures](FAILURES.md).
    - Use the bundled SDF and bridge YAML together only as a starting pair; keep
      their topic and frame names synchronized and verify syntax upstream.
    
    Move to `navigation` only after the robot pose, sensor, map, and velocity
    interfaces are valid. Move to `ros2` when Gazebo is publishing correctly and
    the fault is in the ROS graph. Use `environments` for container GPU/display
    setup and `simulation` only when the simulator itself has not been chosen.
    
    ## Done
    
    - Physics time advances and the expected world and model are present.
    - Every required Gazebo topic has a matching, correctly directed ROS interface.
    - Sensor rate, frame, timestamp, and render output are believable.
    - The run works in its intended graphical or headless environment.
    - Release-specific claims match current [Gazebo](https://gazebosim.org/docs/)
      and [ros_gz](https://github.com/gazebosim/ros_gz) documentation.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related