ground truth ============ The simulator knows every body's exact pose. Two mechanisms expose that truth to ROS, for two different consumers. Ground-truth topic namespace (``ros2_bridge`` ``gt:``) ------------------------------------------------------ The :doc:`ros2_bridge ` can divert *output* topics under a ground-truth prefix so a consumer can tell a true pose from real perception. Configure it on the bridge plugin:: components: - ros2_bridge: gt: prefix: /gt # published outputs move to /gt/... (e.g. /gt/tf) exempt: [odom, scan] # ...except these, which mirror a real topic and stay canonical The rule: **prefix** a pure ground-truth stream that has no real-sensor equivalent (an object's true pose); **exempt** a stream that mirrors a real topic (robot telemetry, a sensor's own message), so it keeps its canonical name. With no ``gt`` block every topic is canonical. Ground-truth base pose (``ground_truth_pose`` plugin) ----------------------------------------------------- ``ground_truth_pose`` (in ``roqsim_sensors``) publishes a robot base body's *true* world pose as a TF transform `` -> ``, default ``map -> _base_link_gt``. It reads ``data.xpos``/``data.xquat`` directly, so it is robot-family-agnostic (TurtleBot, Husky, Spot, a humanoid — same plugin). The frame is a **disconnected leaf**: it is deliberately *not* wired into the odometry/localization chain. nav2 localizes with AMCL off the drifting wheel odometry (``diff_drive`` integrates wheel velocities, so ``/odom`` and ``odom -> base_link`` drift like real hardware); ``map -> odom`` is AMCL's correction. ``_base_link_gt`` sits beside that tree purely so an evaluator can diff the navigated path against the truth. This mirrors the Gazebo navigation stack, where ``gazebo_tf_publisher`` republishes ``SceneBroadcaster`` poses as ``_base_link_gt`` on ``/tf``. Recording ``/tf`` against either simulator therefore yields the same ground-truth frame and the same offline analysis applies unchanged — which is what lets roqsim stand in for Gazebo. Add it to a world (or a robot manifest):: components: - spawn_robot: {model: turtlebot4} name: robot components: - ground_truth_pose: {} # -> /tf: map -> turtlebot4_base_link_gt - ros2_bridge: {} Config keys: ``body`` (base-body override; default the entity's registered base body -- the entity itself is the entry this one is nested under), ``site`` (a site of the entity instead of a body), ``relative_to`` (``world``, the default, or ``base``), ``frame_id`` (parent, default ``map``, or the base body for a relative pose), ``child_frame`` (default ``_base_link_gt`` for a body, the site's own name for a site), ``rate_hz`` (default ``30``), and the standard ``topics:`` hardwire map (``pose`` role; default relative ``tf`` → ``/tf``, or ``/gt/tf`` under the bridge ``gt`` prefix). **A site, and a pose relative to the base.** A robot's real description hangs sensors, emitters and receivers off its base as links, and a stack that reproduces a device from ground truth -- an optical-flow sensor from the true motion of its mount, a dock's infrared field from the true poses of emitter and receiver -- reads those links' poses off the simulator. Gazebo publishes a model's pose in the world and its links' poses *relative to the model*, and the consumer composes the two. The same plugin serves that, several instances on one entity, each its own frame:: components: - spawn_robot: {model: turtlebot4} name: robot components: - ground_truth_pose: {child_frame: turtlebot4, rate_hz: 62, topics: {pose: _internal/sim_ground_truth_pose}} name: gt_base - ground_truth_pose: {site: mouse, relative_to: base, rate_hz: 62, topics: {pose: _internal/sim_ground_truth_pose}} name: gt_mouse A site is published under its own name, the parent of a relative pose defaults to the base body, and the numbers are ``base^-1 * pose`` -- constant for a rigid mount wherever the base stands. It works under a ``spawn_model`` prop as under a robot, which is how a dock's emitter gets a frame.