Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
601fc0f
feat(microduck): simulate pollen-robotics's Microduck biped with nav …
aromeoes Sep 1, 2026
a1465fe
feat(microduck): simplify demo room to red/blue boxes + video demo to…
aromeoes Sep 1, 2026
d40f704
feat(microduck): side-by-side demo render (sim + humancli panel)
aromeoes Sep 1, 2026
ef8a9aa
feat(microduck): browser cockpit with teleop/agent modes, policies, p…
aromeoes Sep 2, 2026
54d610d
docs(microduck): web cockpit runbook
aromeoes Sep 2, 2026
e542fd0
Merge origin/main; move the microduck cockpit onto the Channel/codec API
aromeoes Sep 2, 2026
c8c988f
fix(microduck): make the cockpit blueprint discoverable, and unalias …
aromeoes Sep 3, 2026
aa5b7b9
perf(microduck): stop the state channels starving the cameras
aromeoes Sep 3, 2026
8ffd64a
fix(cockpit): say what the robot is doing instead of only knowing it
aromeoes Sep 3, 2026
1466aee
feat(microduck): make the duck's own view the cockpit's main camera
aromeoes Sep 3, 2026
befe4b9
feat(cockpit): picture-in-picture video, and stop the stalls that rea…
aromeoes Sep 3, 2026
6cf4b60
fix(cockpit): stop the false "stale" on the chase cam and the nav map
aromeoes Sep 3, 2026
3f1d4b7
chore(cockpit): tint panel headers by provenance
aromeoes Sep 3, 2026
ba848c2
perf(microduck): cut the byte budget that was freezing the relay->bro…
aromeoes Sep 3, 2026
4a7309a
perf(web,sim): 3x cockpit video by fixing latest delivery and skippin…
aromeoes Sep 4, 2026
d2f37f8
fix(sim): let camera renders yield to the clock instead of slowing time
aromeoes Sep 4, 2026
93bfd47
chore: drop a scratch benchmark file committed by accident
aromeoes Sep 4, 2026
fb156b8
test(sim): pin that the loop paces against a deadline, not per iteration
aromeoes Sep 4, 2026
ad5c03d
fix(web,sim): act on an external review - monotonic pacing, honest co…
aromeoes Sep 4, 2026
a88db7a
refactor(microduck): one clearance descriptor instead of copied const…
aromeoes Sep 4, 2026
ec6f903
refactor(microduck): move under the pollen vendor namespace
aromeoes Sep 4, 2026
536679f
fix(sim): make the render_depth invariant fail loudly, not silently
aromeoes Sep 4, 2026
2342524
feat: expose hosted cockpit and independent robot extension points
aromeoes Sep 6, 2026
44deb35
Merge main into Microduck cockpit and regenerate blueprints
aromeoes Sep 6, 2026
b3220ba
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 6, 2026
003c126
fix(microduck): complete CI type stubs and web formatting
aromeoes Sep 6, 2026
e0676c6
Merge main and checkpoint Microduck cockpit compatibility
aromeoes Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions MUJOCO_LOG.TXT
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Fri Sep 4 00:23:55 2026
WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited

Fri Sep 4 09:09:16 2026
WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited

Fri Sep 4 11:09:46 2026
WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited

Fri Sep 4 13:23:42 2026
WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited

Fri Sep 4 13:23:58 2026
WARNING: ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited

21 changes: 17 additions & 4 deletions dimos/navigation/replanning_a_star/global_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ class GlobalPlanner(Resource):
_max_path_deviation: float = 0.9
_replanning_enabled: bool = True

def __init__(self, global_config: GlobalConfig) -> None:
def __init__(
self,
global_config: GlobalConfig,
stuck_time_window: float | None = None,
stuck_threshold: float | None = None,
) -> None:
self.path = Subject()
self.goal_reached = Subject()

Expand All @@ -85,9 +90,17 @@ def __init__(self, global_config: GlobalConfig) -> None:
self._global_config, self._navigation_map, self._goal_tolerance
)

stuck_threshold = self._stuck_threshold
if global_config.simulation:
stuck_threshold = 1.0
# Stuck detection: the robot is stuck when every position over the
# last `stuck_time_window` seconds stays within `stuck_threshold` of
# their centroid. The defaults suit a ~0.5 m/s robot; slow platforms
# pass their own values so normal progress doesn't read as stuck.
if stuck_time_window is not None:
self._stuck_time_window = stuck_time_window
if stuck_threshold is None:
stuck_threshold = self._stuck_threshold
if global_config.simulation:
stuck_threshold = 1.0
self._stuck_threshold = stuck_threshold

self._position_tracker = PositionTracker(self._stuck_time_window, stuck_threshold)
self._replan_limiter = ReplanLimiter()
Expand Down
10 changes: 9 additions & 1 deletion dimos/navigation/replanning_a_star/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
class ReplanningAStarPlannerConfig(ModuleConfig):
robot_width: float | None = None
robot_rotation_diameter: float | None = None
# Stuck detector overrides (seconds, metres); None keeps the planner's
# defaults, which assume a robot that covers well over 0.4 m in 8 s.
stuck_time_window: float | None = None
stuck_threshold: float | None = None


class ReplanningAStarPlanner(Module, NavigationInterface):
Expand Down Expand Up @@ -71,7 +75,11 @@ def __init__(self, **kwargs: Any) -> None:
effective_global_config = (
self.config.g.model_copy(update=overrides) if overrides else self.config.g
)
self._planner = GlobalPlanner(effective_global_config)
self._planner = GlobalPlanner(
effective_global_config,
stuck_time_window=self.config.stuck_time_window,
stuck_threshold=self.config.stuck_threshold,
)

@rpc
def start(self) -> None:
Expand Down
44 changes: 44 additions & 0 deletions dimos/navigation/replanning_a_star/test_global_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,47 @@ def test_find_wide_path_with_start_inside_inflation() -> None:

assert path is not None
assert len(path.poses) > 0


def test_stuck_detector_defaults_and_simulation_override() -> None:
planner = GlobalPlanner(GlobalConfig())
assert planner._position_tracker._time_window == 8.0
assert planner._position_tracker._threshold == 0.4

sim = GlobalPlanner(GlobalConfig(simulation="mujoco"))
assert sim._position_tracker._threshold == 1.0


def test_stuck_detector_accepts_explicit_window_and_threshold() -> None:
"""Slow robots (the microduck walks ~0.06 m/s) pass their own values so
normal progress does not trip the detector; explicit values also beat
the simulation override."""
planner = GlobalPlanner(
GlobalConfig(simulation="mujoco"), stuck_time_window=10.0, stuck_threshold=0.15
)
assert planner._stuck_time_window == 10.0
assert planner._position_tracker._time_window == 10.0
assert planner._position_tracker._threshold == 0.15

# Either value may be given on its own.
window_only = GlobalPlanner(GlobalConfig(), stuck_time_window=12.0)
assert window_only._position_tracker._time_window == 12.0
assert window_only._position_tracker._threshold == 0.4


def test_module_config_threads_stuck_detector_into_global_planner() -> None:
from dimos.navigation.replanning_a_star.module import ReplanningAStarPlanner

module = ReplanningAStarPlanner(stuck_time_window=10.0, stuck_threshold=0.15)
try:
assert module._planner._position_tracker._time_window == 10.0
assert module._planner._position_tracker._threshold == 0.15
finally:
module._close_module()

default = ReplanningAStarPlanner()
try:
assert default._planner._position_tracker._time_window == 8.0
assert default._planner._position_tracker._threshold == 0.4
finally:
default._close_module()
8 changes: 8 additions & 0 deletions dimos/robot/all_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
"keyboard-teleop-xarm7": "dimos.robot.manipulators.xarm.blueprints.teleop:keyboard_teleop_xarm7",
"learning-collect-quest-piper": "dimos.imitation.collection.blueprint:learning_collect_quest_piper",
"learning-collect-quest-xarm7": "dimos.imitation.collection.blueprint:learning_collect_quest_xarm7",
"microduck-agentic-sim": "dimos.robot.pollen.microduck.blueprints.microduck_agentic_sim:microduck_agentic_sim",
"microduck-agentic-sim-ollama": "dimos.robot.pollen.microduck.blueprints.microduck_agentic_sim:microduck_agentic_sim_ollama",
"microduck-cockpit-sim": "dimos.robot.pollen.microduck.blueprints.microduck_cockpit_sim:microduck_cockpit_sim",
"microduck-cockpit-sim-ollama": "dimos.robot.pollen.microduck.blueprints.microduck_cockpit_sim:microduck_cockpit_sim_ollama",
"microduck-sim": "dimos.robot.pollen.microduck.blueprints.microduck_sim:microduck_sim",
"mid360": "dimos.hardware.sensors.lidar.livox.livox_blueprints:mid360",
"mid360-fastlio": "dimos.hardware.sensors.lidar.fastlio2.fastlio_blueprints:mid360_fastlio",
"mid360-fastlio-ray-trace": "dimos.hardware.sensors.lidar.fastlio2.fastlio_blueprints:mid360_fastlio_ray_trace",
Expand Down Expand Up @@ -193,6 +198,7 @@
"drone-camera-module": "dimos.robot.drone.camera_module.DroneCameraModule",
"drone-connection-module": "dimos.robot.drone.connection_module.DroneConnectionModule",
"drone-tracking-module": "dimos.robot.drone.drone_tracking_module.DroneTrackingModule",
"duck-control-module": "dimos.robot.pollen.microduck.control_module.DuckControlModule",
"emitter-module": "dimos.utils.demo_image_encoding.EmitterModule",
"episode-monitor-module": "dimos.imitation.collection.episode_monitor.EpisodeMonitorModule",
"eval-module": "dimos.evals.module.EvalModule",
Expand Down Expand Up @@ -237,6 +243,8 @@
"mcp-client": "dimos.agents.mcp.mcp_client.McpClient",
"mcp-server": "dimos.agents.mcp.mcp_server.McpServer",
"memory-module": "dimos.memory.module.MemoryModule",
"microduck-sim-module": "dimos.robot.pollen.microduck.sim_module.MicroduckSimModule",
"microduck-skill-container": "dimos.robot.pollen.microduck.skills.MicroduckSkillContainer",
"mid360-pcap-recorder": "dimos.hardware.sensors.lidar.virtual_mid360.recorder.Mid360PcapRecorder",
"mid360-realsense-recorder": "dimos.robot.assembly.mid360_realsense_30.Mid360RealsenseRecorder",
"mid360-realsense-static-tf": "dimos.robot.assembly.mid360_realsense_30.Mid360RealsenseStaticTf",
Expand Down
136 changes: 136 additions & 0 deletions dimos/robot/pollen/microduck/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Microduck simulation

[Microduck](https://github.com/pollen-robotics/microduck) — pollen-robotics's
~25 cm, ~800 g open-source biped — walking in a small MuJoCo room with the
standard dimOS navigation stack and agent on top of its pretrained RL gait.

```
humancli -> /human_input -> McpClient (LLM) -> skills
begin_exploration / go_to_object / move_to / ...
-> WavefrontFrontierExplorer / ReplanningAStarPlanner
-> MovementManager -> cmd_vel
-> alpha_walking.onnx (50 Hz) -> MuJoCo (200 Hz)
```

## Quick start

```bash
# Simulation + nav only (drive it from `dimos shell` / planner RPCs):
dimos --viewer none run microduck-sim

# With the agent, using a local LLM via ollama (pulls qwen3:8b on first use):
dimos --viewer none run microduck-agentic-sim-ollama
# ...or with OPENAI_API_KEY set:
dimos --viewer none run microduck-agentic-sim

# In a second terminal:
humancli
> explore the room for 30 seconds, then walk to the red ball
```

On first start the robot model/meshes and walking policy (~26 MB) are
downloaded from the public pollen-robotics GitHub repos into
`~/.cache/dimos/microduck` (see `assets_fetch.py`; `DIMOS_MICRODUCK_ASSETS`
overrides the location, and the fetch is pinned to upstream commits).

If module startup hangs with `RPC call ... timed out` on macOS with
Tailscale (or other VPNs that own the multicast route), zenoh's
loopback-only peer discovery is broken on your machine; run everything with
`--zenoh-scouting` (and `ZENOH_SCOUTING=true humancli`), or put
`zenoh_scouting=true` in your `.env`.

## Web cockpit (`microduck-cockpit-sim`)

A four-room flat (kitchen = "space A", living = "space B", bedroom =
"space C", office = "space D", around an open hub) driven from the browser:
WASDQE teleop, one button per RL policy, a teleop/agent switch, the agent's
humancli transcript, and click-to-goal on the costmap through the standard
`ReplanningAStarPlanner`.

```bash
# OPENAI_API_KEY must be in the environment (the ollama variant needs no key):
dimos --viewer none run microduck-cockpit-sim --local-relay
dimos --viewer none run microduck-cockpit-sim-ollama --local-relay
# then open http://127.0.0.1:7780 (localhost only; the URL is also logged)

MICRODUCK_VARIANT=rollers dimos --viewer none run microduck-cockpit-sim --local-relay
```

Stop any other microduck blueprint first - they share topic names on the
zenoh router. On macOS with Tailscale, add `--zenoh-scouting` as above.

Panels:

- **Control strip** - `Teleop` / `Agent` mode, one button per policy
(`walk`, `stand`, `roller*`, `sitstand`, `kick_left/right`, `roulade`,
`ground_pick`; greyed with a reason when the variant or the asset lacks
it), the nav state chip and its cancel (✕).
- **Nav map** - costmap, rooms, landmark objects, remembered places, the
planner's path and goal. Click anywhere to send a goal, click a room label
to go to that room's target, `Esc` (map focused) or ✕ to cancel.
- **Teleop (WASDQE)** - W/S forward-back, A/D strafe, Q/E yaw; only acts in
teleop mode, and only while the tab is in the foreground (browsers
throttle background tabs; the deadman then zeroes the twist).
- **Chat** - the agent transcript (tool calls and results included) with an
input box, live in agent mode. Try `go to space A`, `go to the kitchen`,
`walk to the blue box`, `remember this place as fridge`, `what do you
see?`, `do a roulade`.
- **Chase cam / Head cam** - decoded only while the tab is visible.

In teleop mode the planner's `nav_cmd_vel` is muted (a click still plans
and draws the path); switching to agent mode hands `cmd_vel` to the planner
and enables the chat. Navigation is locked (and any active goal cancelled)
while a one-shot policy runs, while the duck is seated, fallen or standing
back up; the chip shows the reason.

## What's in the box

- `sim_module.py` — `MicroduckSimModule(MujocoSimModule)`: composes the room
scene + robot MJCF (adding three trunk-mounted raycast-lidar cameras),
runs the ONNX walking policy in the engine's step hook at 50 Hz, and maps
`cmd_vel` twists into the policy's command space. No ControlCoordinator:
the whole robot is one module. Odom/tf/IMU/pointcloud publishing is
inherited from `MujocoSimModule`.
- `gait.py` — the 61-dim observation contract of the alpha policies
(documented in microduck's `duck-control/src/obs.rs`), with joint order,
home pose and action scale read from the ONNX metadata.
- `skills.py` — `MicroduckSkillContainer`: `go_to_object` / `list_objects` /
`move_to` / `where_am_i` / `stop_moving` / `wait`. Object positions are the
scene's ground truth (configured in the blueprint), not perception — this
is the deliberately-basic demo.
- `assets/room_scene.xml` — a 4 x 3 m walled room with four colored objects.
World geometry is geom group 0; the lidar only raycasts group 0, so the
robot (groups 2/3) never sees itself.
- `web_codecs.py` — the cockpit's own `@web_encoder`s (the transcript, the
planner path, the JSON-string state streams). The blueprint declares the
matching streams as `Channel(...)` and `cockpit()` generates a relay-bridge
subclass with a typed port for each, so none of this robot's vocabulary -
or its langchain dependency - lands in `dimos.web`.
- `blueprints/` — `microduck-sim` (sim + nav + explorer),
`microduck-agentic-sim[-ollama]` (adds McpServer/McpClient + skills) and
`microduck-cockpit-sim[-ollama]` (the web cockpit above).

## Quirks worth knowing

- **Command shaping** (`sim_module.py`): the policy tracks its velocity
command with a ~2.5x undershoot, so requested twists are multiplied up
(`cmd_gain_linear/angular`), and it has a yaw deadband — pure-turn
commands below the top of its range barely rotate it (~3-9 deg/s at
1.0 rad/s vs 25-31 deg/s at 1.5) — so turn commands are bumped to
`min_effective_wz` (1.5, the range maximum).
- **Falls**: only the plain walking policy is published (no fall recovery),
and the walk-optimized model has no trunk collision geoms. When the trunk
stays tilted > ~55 degrees for 2 s the module stands the duck back up,
nudged toward the room origin so it doesn't re-spawn wedged inside
whatever it tripped over.
- **Rendered cameras are Linux-only**: `mujoco.Renderer` needs a GL context
that macOS only allows on the main thread; creating one from the engine's
sim thread deadlocks the worker. The raycast lidar is pure `mj_ray` and
works everywhere; the blueprint enables `color_image` only off-macOS.
- The planner floors commands at 0.2 m/s; the duck actually walks ~0.1 m/s,
so room crossings take a minute or two. That's the robot, not a bug.

## Licensing

Code in the upstream microduck repos is Apache-2.0; the 3D model files are
CC BY-NC-SA — they are downloaded to a local cache, not redistributed here.
97 changes: 97 additions & 0 deletions dimos/robot/pollen/microduck/assets/four_room_scene.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<mujoco model="microduck_four_rooms">
<!-- A 4 x 4 m arena for the Microduck (a ~25 cm biped) split into four
quadrant "rooms" by short wall stubs, with the central 1.6 x 1.6 m hub
left open so every room is reachable from every other one. The world
frame is the MuJoCo frame is the odom frame.

Rooms (see dimos/robot/microduck/places.py, which must stay in sync):
kitchen (space A) x in [ 0, 2], y in [ 0, 2] (+x, +y)
living (space B) x in [-2, 0], y in [ 0, 2] (-x, +y)
bedroom (space C) x in [-2, 0], y in [-2, 0] (-x, -y)
office (space D) x in [ 0, 2], y in [-2, 0] (+x, -y)

All static world geometry lives in geom group 0 so the raycast lidar
can select it while ignoring the robot (visual group 2 / collision
group 3). Object poses are mirrored in MICRODUCK_OBJECTS; a unit test
parses this file and cross-checks them.

This file must stay free of joints: MuJoCo numbers joints in body
order and the simulation engine takes joint 0 as the robot's root
free joint, so anything jointed declared here would be mistaken for
the robot (odom, spawn, IMU fallback, lidar exclusion). The kickable
ball is therefore added by the sim module AFTER the robot is attached
(dimos.robot.microduck.places.add_ball_body), not declared here. -->
<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/>
<global azimuth="150" elevation="-25" offwidth="1280" offheight="720"/>
</visual>

<asset>
<texture type="skybox" builtin="gradient" rgb1="0.4 0.55 0.7" rgb2="0.05 0.05 0.1"
width="512" height="3072"/>
<texture type="2d" name="floortex" builtin="checker" mark="edge" rgb1="0.32 0.3 0.28"
rgb2="0.24 0.22 0.2" markrgb="0.5 0.5 0.5" width="300" height="300"/>
<material name="floormat" texture="floortex" texuniform="true" texrepeat="8 8"
reflectance="0.15"/>
<material name="wallmat" rgba="0.75 0.73 0.68 1"/>
<material name="stubmat" rgba="0.62 0.6 0.56 1"/>
<material name="kitchenmat" rgba="0.85 0.72 0.35 1"/>
<material name="livingmat" rgba="0.35 0.62 0.85 1"/>
<material name="bedroommat" rgba="0.62 0.42 0.78 1"/>
<material name="officemat" rgba="0.42 0.75 0.48 1"/>
</asset>

<worldbody>
<light name="sun_a" pos="1.5 1.5 3.0" dir="-0.35 -0.35 -1" directional="true"
diffuse="0.55 0.55 0.55" specular="0.1 0.1 0.1"/>
<light name="sun_b" pos="-1.5 -1.5 3.0" dir="0.35 0.35 -1" directional="true"
diffuse="0.45 0.45 0.45" specular="0.05 0.05 0.05"/>

<geom name="floor" type="plane" size="2.2 2.2 0.05" material="floormat" group="0"/>

<!-- Coloured floor patches (visual only) so the rooms are recognisable
from the chase camera. -->
<geom name="floor_kitchen" type="box" size="0.98 0.98 0.001" pos="1 1 0.001"
material="kitchenmat" contype="0" conaffinity="0" group="0"/>
<geom name="floor_living" type="box" size="0.98 0.98 0.001" pos="-1 1 0.001"
material="livingmat" contype="0" conaffinity="0" group="0"/>
<geom name="floor_bedroom" type="box" size="0.98 0.98 0.001" pos="-1 -1 0.001"
material="bedroommat" contype="0" conaffinity="0" group="0"/>
<geom name="floor_office" type="box" size="0.98 0.98 0.001" pos="1 -1 0.001"
material="officemat" contype="0" conaffinity="0" group="0"/>

<!-- Outer walls: 0.5 m tall, 0.1 m thick, inner faces at +/-2.0 m. -->
<geom name="wall_north" type="box" size="2.1 0.05 0.25" pos="0 2.05 0.25"
material="wallmat" group="0"/>
<geom name="wall_south" type="box" size="2.1 0.05 0.25" pos="0 -2.05 0.25"
material="wallmat" group="0"/>
<geom name="wall_east" type="box" size="0.05 2.0 0.25" pos="2.05 0 0.25"
material="wallmat" group="0"/>
<geom name="wall_west" type="box" size="0.05 2.0 0.25" pos="-2.05 0 0.25"
material="wallmat" group="0"/>

<!-- Inner stubs: split the arena into quadrants, leaving a 1.6 m gap
between stub tips (the open central hub). -->
<geom name="stub_east" type="box" size="0.6 0.05 0.25" pos="1.4 0 0.25"
material="stubmat" group="0"/>
<geom name="stub_west" type="box" size="0.6 0.05 0.25" pos="-1.4 0 0.25"
material="stubmat" group="0"/>
<geom name="stub_north" type="box" size="0.05 0.6 0.25" pos="0 1.4 0.25"
material="stubmat" group="0"/>
<geom name="stub_south" type="box" size="0.05 0.6 0.25" pos="0 -1.4 0.25"
material="stubmat" group="0"/>

<!-- Landmark objects, one per room plus one near the kitchen's back
wall. Sized 6-14 cm so the lidar maps them. -->
<geom name="red_box" type="box" size="0.07 0.07 0.07" pos="1.5 1.5 0.07"
rgba="0.85 0.1 0.1 1" group="0"/>
<geom name="blue_box" type="box" size="0.07 0.07 0.07" pos="-1.5 1.5 0.07"
rgba="0.1 0.25 0.85 1" group="0"/>
<geom name="green_cylinder" type="cylinder" size="0.06 0.1" pos="-1.5 -1.5 0.1"
rgba="0.1 0.7 0.2 1" group="0"/>
<geom name="yellow_pillar" type="cylinder" size="0.05 0.14" pos="1.5 -1.5 0.14"
rgba="0.95 0.85 0.1 1" group="0"/>
<geom name="orange_crate" type="box" size="0.06 0.09 0.06" pos="0.6 1.7 0.06"
rgba="0.95 0.5 0.1 1" group="0"/>
</worldbody>
</mujoco>
Loading
Loading